N
Nuxt2mo ago
psycarlo

Nuxt Server: Extend H3Event with Typesafety

I am trying to create a util like https://nuxt.com/docs/4.x/guide/directory-structure/server#server-utilities so I can reuse validation code like:
const user = await serverSupabaseUser(event)
if (!user) {
return sendError(event, createError({
statusCode: 401,
statusMessage: 'Unauthorized'
}))
}
const user = await serverSupabaseUser(event)
if (!user) {
return sendError(event, createError({
statusCode: 401,
statusMessage: 'Unauthorized'
}))
}
or
const paramsValidation = await getValidatedRouterParams(event, ValidIdUUID.safeParse)
if (!paramsValidation.success) {
return sendError(event, createError({
statusCode: 400,
statusMessage: `${paramsValidation.error}`
}))
}
const paramsValidation = await getValidatedRouterParams(event, ValidIdUUID.safeParse)
if (!paramsValidation.success) {
return sendError(event, createError({
statusCode: 400,
statusMessage: `${paramsValidation.error}`
}))
}
The util handler would take:
{ user?: boolean // Validate the user or not, paramsValidation?: zod.safeParse, etc... }
{ user?: boolean // Validate the user or not, paramsValidation?: zod.safeParse, etc... }
as first argument, and then inject into event.context. Then on the endpoint handler itself, event.context.user would be type User if we passed { user: true } to validate it. Same for params, we would get the parsedData from zod. Is this possible? Some examples out there?
Nuxt
server · Nuxt Directory Structure v4
The server/ directory is used to register API and server handlers to your application.
2 Replies
kapa.ai
kapa.ai2mo ago
Hi @psycarlo. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You can absolutely create a reusable utility in Nuxt to handle validation and inject typed data into event.context, but the official documentation does not provide a full, type-safe example for extending H3Event's context in a generic way. However, the pattern you describe—using server utilities to wrap handlers and inject validated data into event.context—is supported and encouraged in Nuxt. How to Approach This 1. Custom Server Utilities:
You can define a utility in ~/server/utils that wraps your handler, performs validation (e.g., user session, params), and attaches the results to event.context. This is shown in the docs as a general pattern, though not with full type inference for event.context based on the utility's options.
// server/utils/handler.ts
import type { EventHandler, EventHandlerRequest } from 'h3'

export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D>(
handler: EventHandler<T, D>,
): EventHandler<T, D> =>
defineEventHandler<T>(async (event) => {
try {
// do something before the route handler
const response = await handler(event)
// do something after the route handler
return { response }
} catch (err) {
// Error handling
return { err }
}
})

// server/utils/handler.ts
import type { EventHandler, EventHandlerRequest } from 'h3'

export const defineWrappedResponseHandler = <T extends EventHandlerRequest, D>(
handler: EventHandler<T, D>,
): EventHandler<T, D> =>
defineEventHandler<T>(async (event) => {
try {
// do something before the route handler
const response = await handler(event)
// do something after the route handler
return { response }
} catch (err) {
// Error handling
return { err }
}
})

Nuxt 4.x Server Utilities Example 2. Validation Helpers:
The docs show using helpers like requireUserSession (for user validation) and getValidatedRouterParams (for params validation with Zod), which throw or return errors if validation fails. You can compose these in your utility.
// Example usage in a route
export default defineEventHandler(async (event) => {
const { user } = await requireUserSession(event)
const params = await getValidatedRouterParams(event, schema.safeParse)
// ... your logic
})

// Example usage in a route
export default defineEventHandler(async (event) => {
const { user } = await requireUserSession(event)
const params = await getValidatedRouterParams(event, schema.safeParse)
// ... your logic
})

Protect API Routes 3. Type Safety:
While you can extend event.context at runtime, TypeScript will not automatically infer the new types unless you augment the H3 types globally or use type assertions in your handlers. The docs do not provide a ready-made solution for dynamic type inference based on utility options. Example Pattern You can create a utility like this (simplified):
// server/utils/withValidation.ts
import { requireUserSession } from '#auth-utils'
import { getValidatedRouterParams } from '#h3-helpers'
import type { H3Event } from 'h3'
import type { ZodSchema } from 'zod'

interface Options {
user?: boolean
paramsSchema?: ZodSchema<any>
}

export function withValidation<T>(
handler: (event: H3Event) => Promise<T>,
options: Options
) {
return defineEventHandler(async (event) => {
if (options.user) {
const { user } = await requireUserSession(event)
event.context.user = user // TypeScript will not infer this automatically
}
if (options.paramsSchema) {
const params = await getValidatedRouterParams(event, options.paramsSchema.safeParse)
event.context.params = params
}
return handler(event)
})
}
// server/utils/withValidation.ts
import { requireUserSession } from '#auth-utils'
import { getValidatedRouterParams } from '#h3-helpers'
import type { H3Event } from 'h3'
import type { ZodSchema } from 'zod'

interface Options {
user?: boolean
paramsSchema?: ZodSchema<any>
}

export function withValidation<T>(
handler: (event: H3Event) => Promise<T>,
options: Options
) {
return defineEventHandler(async (event) => {
if (options.user) {
const { user } = await requireUserSession(event)
event.context.user = user // TypeScript will not infer this automatically
}
if (options.paramsSchema) {
const params = await getValidatedRouterParams(event, options.paramsSchema.safeParse)
event.context.params = params
}
return handler(event)
})
}
Note:
- You will need to use type assertions or augment the H3 types to get full type safety in your handlers. - The official docs show the runtime pattern, but not a fully type-safe, generic utility as you describe. Summary - The pattern of wrapping handlers for validation and injecting into event.context is supported and shown in the docs. - Full compile-time type safety for event.context based on utility options is not provided out-of-the-box in the docs. - You can use type assertions or global type augmentation for better type safety, but this is not covered in the official examples. For more, see the Nuxt 4.x server utilities documentation and Protect API Routes. If you need a fully type-safe, generic utility, you may need to implement additional TypeScript type augmentation yourself, as the knowledge sources do not provide a complete example for this. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2mo ago
<@277428305756422144> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?