# 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' } }, }), ) ```