# H3 / Nuxt Adapter

## Setup

Install H3 as a peer dependency:

```bash
pnpm add h3
```

The adapter exports its own `withSupabase` that returns H3 middleware instead of a fetch handler. Works with standalone H3 servers and Nuxt server routes (which run on H3 under the hood).

## Typing `event.context.supabaseContext`

The middleware stores the context on `event.context.supabaseContext`. Add this declaration once in your project (e.g. in `types/h3.d.ts`) for typed access:

```ts
import type { SupabaseContext } from '@supabase/server'

declare module 'h3' {
  interface H3EventContext {
    supabaseContext: SupabaseContext
  }
}
```

## Basic app with auth

```ts
import { H3 } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'

const app = new H3()

// Apply auth to all routes
app.use(withSupabase({ auth: 'user' }))

app.get('/todos', async (event) => {
  const { supabase } = event.context.supabaseContext
  const { data } = await supabase.from('todos').select()
  return data
})

app.get('/profile', async (event) => {
  const { supabase, userClaims } = event.context.supabaseContext
  const { data } = await supabase
    .from('profiles')
    .select()
    .eq('id', userClaims!.id)
  return data
})

export default { fetch: app.fetch }
```

The context is stored in `event.context.supabaseContext` and contains the same `SupabaseContext` fields as the main `withSupabase` wrapper: `supabase`, `supabaseAdmin`, `userClaims`, `jwtClaims`, and `authMode`.

## Per-route auth

Apply different auth modes to different routes by attaching the middleware to specific handlers:

```ts
import { H3 } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'

const app = new H3()

// Public route — no auth
app.get('/health', () => ({ status: 'ok' }))

// User-authenticated route
app.get('/todos', withSupabase({ auth: 'user' }), async (event) => {
  const { supabase } = event.context.supabaseContext
  const { data } = await supabase.from('todos').select()
  return data
})

// Secret-key-protected admin route
app.post('/admin/sync', withSupabase({ auth: 'secret' }), async (event) => {
  const { supabaseAdmin } = event.context.supabaseContext
  const { data } = await supabaseAdmin
    .from('audit_log')
    .insert({ action: 'sync' })
  return data
})

// Dual auth — users or services
app.get(
  '/reports',
  withSupabase({ auth: ['user', 'secret'] }),
  async (event) => {
    const { authMode } = event.context.supabaseContext
    return { authMode }
  },
)

export default { fetch: app.fetch }
```

## Nuxt: file-based routes

Use `defineHandler` to attach auth to a single Nuxt server route:

```ts
// server/api/games.get.ts
import { defineHandler } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'

export default defineHandler({
  middleware: [withSupabase({ auth: 'user' })],
  handler: async (event) => {
    const { supabase } = event.context.supabaseContext
    return supabase.from('favorite_games').select()
  },
})
```

## Nuxt: app-wide auth

Register as a server middleware to apply auth to every route:

```ts
// server/middleware/supabase.ts
import { withSupabase } from '@supabase/server/adapters/h3'

export default withSupabase({ auth: 'user' })
```

## Skip behavior

If a previous middleware already set `event.context.supabaseContext`, subsequent `withSupabase` calls skip auth. This enables a pattern where route-level middleware overrides the app-wide default:

```ts
const app = new H3()

// App-wide: require user auth
app.use(withSupabase({ auth: 'user' }))

// This route needs secret auth instead.
// The route-level middleware runs first, sets the context,
// and the app-wide middleware skips.
app.post('/webhook', withSupabase({ auth: 'secret' }), async (event) => {
  const { supabaseAdmin } = event.context.supabaseContext
  // ...
})
```

## CORS

The H3 adapter does not handle CORS — the `cors` option is excluded from its config type. Use H3's built-in CORS utilities:

```ts
import { H3, handleCors } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'

const app = new H3()

app.use((event) => handleCors(event, { origin: '*' }))
app.use(withSupabase({ auth: 'user' }))

app.get('/todos', async (event) => {
  const { supabase } = event.context.supabaseContext
  const { data } = await supabase.from('todos').select()
  return data
})

export default { fetch: app.fetch }
```

## Environment overrides

Pass `env` to override auto-detected environment variables, same as the main wrapper:

```ts
app.use(
  withSupabase({
    auth: 'user',
    env: { url: 'http://localhost:54321' },
  }),
)
```

## Supabase client options

Forward options to the underlying `createClient()` calls:

```ts
app.use(
  withSupabase({
    auth: 'user',
    supabaseOptions: { db: { schema: 'api' } },
  }),
)
```
