> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corsair.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Nuxt

> Mount Corsair as a Nitro server route, read synced data with useFetch, and connect tenants with the vanilla client.

In Nuxt, anything that talks to a third-party service lives in `server/` and runs on Nitro. Corsair fits that split exactly. The handler is one Nitro route, your pages stay on the client, and the bridge between them is a `useFetch` against a server route that reads Corsair's synced tables. Nothing integration-shaped ever ships to the browser.

## Install

The adapter ships in core. Each integration is its own package. Add one per service you connect.

<CodeGroup>
  ```bash npm theme={null}
  npm i corsair @corsair-dev/github
  ```

  ```bash pnpm theme={null}
  pnpm add corsair @corsair-dev/github
  ```

  ```bash yarn theme={null}
  yarn add corsair @corsair-dev/github
  ```

  ```bash bun theme={null}
  bun add corsair @corsair-dev/github
  ```
</CodeGroup>

Every service is its own `@corsair-dev/*` package. The full catalog with exact ids is in [Plugins](/guides/plugins).

## Create the instance

Build the instance once. The handler and your read/write code both import it.

```ts corsair.ts theme={null}
import Database from 'better-sqlite3';
import { createCorsair } from 'corsair';
import { github } from '@corsair-dev/github';

export const corsair = createCorsair({
    database: new Database('corsair.db'),
    kek: process.env.CORSAIR_KEK!,
    multiTenancy: true,
    plugins: [github({ authType: 'managed' })], // one entry per @corsair-dev/* package you install
});
```

The handler and every `.db` / `.api` call on this page import this `corsair`. `multiTenancy: true` is what makes `corsair.withTenant(id)` available, and a `tenant.github.*` call resolves to the `github()` entry here. Add a service by installing its `@corsair-dev/*` package and dropping it into `plugins`. Database choices and the Hub keys are in [Quick Start](/quick-start).

## Mount the handler

Add a catch-all under `server/routes/`. Nitro passes the request straight to the adapter.

```ts server/routes/api/corsair/[...].ts theme={null}
import { toNuxtHandler } from 'corsair';
import { corsair } from '~/server/corsair';

export default toNuxtHandler(corsair, { basePath: '/api/corsair' });
```

`~/server/corsair` is your `createCorsair({ ... })` instance. See [Getting Started](/quick-start) if you haven't built it yet.

<Note>The `[...]` catch-all matters: Corsair serves delivery, connect, and tenant paths all under `/api/corsair`, so the route has to swallow the whole subtree.</Note>

## Resolve the tenant

Every read and write is scoped to a tenant id, whatever stable id identifies the current user or org. In a Nitro route you already have the h3 `event`, so resolve the id from your session or a header (however your auth works) and pass it to `withTenant`.

```ts theme={null}
export default defineEventHandler(async (event) => {
    const tenantId = await getTenantId(event); // getTenantId is YOUR code
    const tenant = corsair.withTenant(tenantId);
    // ...
});
```

## Read and write

Reads go through `.db`, your own database with no rate limits, so keep them in a Nitro server route and pull them into a page with `useFetch`. Writes go through `.api`, and Corsair upserts the response, so the next read already reflects it.

```ts server/api/issues.get.ts theme={null}
import { corsair } from '~/server/corsair';

export default defineEventHandler(async (event) => {
    const tenantId = await getTenantId(event);
    const tenant = corsair.withTenant(tenantId);
    const issues = await tenant.github.db.issues.search({
        data: { state: 'open' },
        limit: 50,
    });
    return issues.map((i) => ({ title: i.data.title, user: i.data.user?.login }));
});
```

```vue app.vue theme={null}
<script setup lang="ts">
const { data: issues } = await useFetch('/api/issues');
</script>

<template>
  <li v-for="i in issues" :key="i.title">{{ i.title }} — {{ i.user }}</li>
</template>
```

A mutation is a `.post` handler that calls `.api`, e.g. `await tenant.github.api.issues.create({ owner: 'acme', repo: 'app', title: '…' })`. Entity fields live on `.data` in camelCase.

## Refresh after a write

The `.api` write already made `.db` current, so the page just needs to re-read. `useFetch` gives you a `refresh` for that. Call it after the mutation resolves, or `refreshNuxtData('issues')` from anywhere by key.

```vue theme={null}
<script setup lang="ts">
const { data: issues, refresh } = await useFetch('/api/issues', { key: 'issues' });

async function close(number: number) {
    await $fetch('/api/issues/close', { method: 'POST', body: { number } });
    await refresh();
}
</script>
```

## Connect a tenant

Nuxt renders Vue, and Corsair's typed hooks client is React-only, so use the framework-agnostic [vanilla client](/adapters/client) for the connect flow. Create it once and call it from a Nitro route or a plain client script; it mints a connect link and reports status without any UI framework.

<Steps>
  <Step title="Create the client">
    ```ts corsair-client.ts theme={null}
    import { createCorsairClient } from "corsair";

    export const client = createCorsairClient({ baseURL: "/api/corsair" });
    ```

    `createCorsairClient` is a typed fetch wrapper over the management API, no React required. Use it from your frontend, a worker, or a script. Every route is typed against its response. Full surface on the [Vanilla Client](/adapters/client) reference.
  </Step>

  <Step title="Read what's connected">
    ```ts connections.ts theme={null}
    const status = await client.connectionStatus.get({ tenantId: "acme" });
    // { github: 'connected', slack: 'not_connected', notion: 'missing_credentials' }
    ```

    `connectionStatus.get` returns a map of plugin id to `connected | missing_credentials | not_connected`, so you can show what's live and what still needs connecting.
  </Step>

  <Step title="Mint a connect link">
    ```ts connect.ts theme={null}
    const { connectUrl } = await client.connect.createLink({
        plugin: "github",
        tenantId: "acme",
    });
    window.location.href = connectUrl; // send the user to Hub's hosted connect page
    ```

    Hub hosts the consent screen and runs the OAuth handshake. When the user returns, the tokens are already encrypted in your own database. You never saw them, and neither did Hub. Swap `"github"` for any [plugin](/guides/plugins) you configured.
  </Step>
</Steps>

## Go green

```bash theme={null}
npm run dev
```

The first request to `/api/corsair` registers your delivery URL with Hub and turns the **App sync** dot in the dashboard header green.

## Deploy

Deploy the Nuxt app as usual, and the Nitro server route ships with it. In production, `/api/corsair` at your deployed origin is the delivery URL Hub calls, so nothing extra to wire up.

## Next

<CardGroup cols={2}>
  <Card title="Build a dashboard" icon="table-columns" href="/use-cases/dashboards">
    The full read-`.db`, write-`.api` pattern behind the example above.
  </Card>

  <Card title="Connect / OAuth" icon="link" href="/management/connect">
    The full connect flow, error codes, and retry.
  </Card>

  <Card title="Use with an agent" icon="robot" href="/mcp-adapters/vercel-ai">
    Give an agent the Corsair tools and let it call any endpoint.
  </Card>

  <Card title="Multi-tenancy" icon="users" href="/concepts/multi-tenancy">
    One flag and every user gets their own data and credentials.
  </Card>
</CardGroup>
