For AI agents: the complete documentation index is available at https://dotpress.dev/llms.txt, the full documentation bundle is available at https://dotpress.dev/llms-full.txt, and this page is available as Markdown at https://dotpress.dev/reference/routes.md.

Routes and groups

defineRoute(), createRouteGroup(), createRouter() and defineMiddleware(). For a guide, see Routes, Route groups and Middlewares.

const 
const loadTenant: Middleware<{
    tenant: Tenant;
}>
loadTenant
=
defineMiddleware<{
    tenant: Tenant;
}>(middleware: (ctx: RequestContext) => MiddlewareResult | Promise<MiddlewareResult>, options?: MiddlewareOptions): Middleware<{
    tenant: Tenant;
}>

Declares a middleware that guarantees context properties to handlers.

@example

const requireTenant = defineMiddleware<{ tenant: Tenant }>((ctx) => ctx.tenant ? undefined : forbiddenError() )

defineRoute({ path: '/settings', middlewares: [requireTenant], handler: ({ tenant }) => tenant.settings, // Tenant, not Tenant | undefined })

// Documented as secured by the bearerAuth scheme (@dotpress/openapi). const requireUser = defineMiddleware((ctx) => ..., { security: ['bearerAuth'] })

defineMiddleware
<{
tenant: Tenant
tenant
: Tenant }>(async (
ctx: RequestContext
ctx
) => {
const
const tenant: Tenant | undefined
tenant
= await
const tenants: {
    find(slug: string): Promise<Tenant | undefined>;
}
tenants
.
function find(slug: string): Promise<Tenant | undefined>
find
(
ctx: RequestContext
ctx
.
RequestContext.req: DotpressRequest<Record<string, string>, QueryValues, unknown, UploadedFiles>
req
.
DotpressRequest<Record<string, string>, QueryValues, unknown, UploadedFiles>.params: Record<string, string>
params
.
string
tenant
)
if (!
const tenant: Tenant | undefined
tenant
) return
function notFoundError(message?: string, data?: ErrorData): HttpError
notFoundError
('Tenant not found')
var Object: ObjectConstructor

Provides functionality common to all JavaScript objects.

Object
.
ObjectConstructor.assign<RequestContext, {
    tenant: Tenant;
}>(target: RequestContext, source: {
    tenant: Tenant;
}): RequestContext & {
    tenant: Tenant;
} (+3 overloads)

Copy the values of all of the enumerable own properties from one or more source objects to a target object. Returns the target object.

@paramtarget The target object to copy to.@paramsource The source object from which to copy properties.
assign
(
ctx: RequestContext
ctx
, {
tenant: Tenant
tenant
})
}) export const
const router: Router
router
=
function createRouter(): Router

Creates an isolated route registry.

createRouter
()
const
const tenantRoutes: RouteGroup<EmptyObject & {
    tenant: string;
}, {
    tenant: Tenant;
}>
tenantRoutes
=
const router: Router
router
.
RouteGroup<EmptyObject, unknown>.createGroup<"/tenants/:tenant", readonly [{
    tenant: Tenant;
}]>(prefix: "/tenants/:tenant", middlewares?: readonly [Middleware<{
    tenant: Tenant;
}>] | undefined): RouteGroup<EmptyObject & {
    tenant: string;
}, {
    tenant: Tenant;
}>

Creates a nested group, inheriting the prefix and middlewares. Context properties guaranteed by the group middlewares are typed in the handlers of its routes.

createGroup
('/tenants/:tenant', [
const loadTenant: Middleware<{
    tenant: Tenant;
}>
loadTenant
])
const tenantRoutes: RouteGroup<EmptyObject & {
    tenant: string;
}, {
    tenant: Tenant;
}>
tenantRoutes
.
RouteGroup<EmptyObject & { tenant: string; }, { tenant: Tenant; }>.defineRoute<"/projects/:id", {
    response: ZodObject<{
        id: ZodString;
        name: ZodString;
    }, $strip>;
}, false, unknown[]>(route: RouteConfig<"/projects/:id", {
    response: ZodObject<{
        id: ZodString;
        name: ZodString;
    }, $strip>;
}, EmptyObject & {
    tenant: string;
}, false, unknown[], {
    tenant: Tenant;
}>): void
defineRoute
({
RouteConfig<"/projects/:id", { response: ZodObject<{ id: ZodString; name: ZodString; }, $strip>; }, EmptyObject & { tenant: string; }, false, unknown[], { tenant: Tenant; }>.path: "/projects/:id"
path
: '/projects/:id',
RouteConfig<"/projects/:id", { response: ZodObject<{ id: ZodString; name: ZodString; }, $strip>; }, EmptyObject & { tenant: string; }, false, unknown[], { tenant: Tenant; }>.schema?: SchemaDefinition<{
    response: ZodObject<{
        id: ZodString;
        name: ZodString;
    }, $strip>;
}> | undefined
schema
: (
z: typeof import("zod")
z
) => ({
response: ZodObject<{
    id: ZodString;
    name: ZodString;
}, $strip>
response
:
z: typeof import("zod")
z
.
function object<{
    id: ZodString;
    name: ZodString;
}>(shape?: {
    id: ZodString;
    name: ZodString;
} | undefined, params?: string | {
    error?: string | $ZodErrorMap<NonNullable<$ZodIssueInvalidType<unknown> | $ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): ZodObject<{
    id: ZodString;
    name: ZodString;
}, $strip>
object
({
id: ZodString
id
:
z: typeof import("zod")
z
.
function string(params?: string | $ZodStringParams): ZodString (+1 overload)
string
(),
name: ZodString
name
:
z: typeof import("zod")
z
.
function string(params?: string | $ZodStringParams): ZodString (+1 overload)
string
() }) }),
RouteConfig<P extends string, S extends RouteSchema, TParentParams = EmptyObject, TRawBody extends boolean = false, TProvided extends readonly unknown[] = unknown[], TGroupContext = unknown>.meta?: RouteMeta | undefined
meta
: {
RouteMeta.summary?: string | undefined
summary
: 'Get a project',
RouteMeta.tags?: string[] | undefined
tags
: ['projects'],
RouteMeta.responses?: Record<number, string | RouteResponse> | undefined

Other responses of the route, by status: errors returned by the handler, or responses that are not JSON (file(), stream(), sse(), redirect()). A description, or a description with a schema or a content type.

@exampleresponses : { 404: 'Member not found', 200: { description: 'CSV export', contentType: 'text/csv' }, }
responses
: { 404: 'Not found' } },
RouteConfig<"/projects/:id", { response: ZodObject<{ id: ZodString; name: ZodString; }, $strip>; }, EmptyObject & { tenant: string; }, false, unknown[], { tenant: Tenant; }>.handler: RouteHandler<{
    response: ZodObject<{
        id: ZodString;
        name: ZodString;
    }, $strip>;
}, {
    tenant: string;
    id: string;
}, false, {
    tenant: Tenant;
}>
handler
: async ({
req: RouteRequest<{
    response: ZodObject<{
        id: ZodString;
        name: ZodString;
    }, $strip>;
}, {
    tenant: string;
    id: string;
}, false>
req
,
tenant: Tenant
tenant
}) =>
(await
const projects: {
    find(tenantId: string, id: string): Promise<{
        id: string;
        name: string;
    } | undefined>;
}
projects
.
function find(tenantId: string, id: string): Promise<{
    id: string;
    name: string;
} | undefined>
find
(
tenant: Tenant
tenant
.
Tenant.id: string
id
,
req: RouteRequest<{
    response: ZodObject<{
        id: ZodString;
        name: ZodString;
    }, $strip>;
}, {
    tenant: string;
    id: string;
}, false>
req
.
DotpressRequest<{ tenant: string; id: string; }, QueryValues, unknown, UploadedFiles>.params: {
    tenant: string;
    id: string;
}
params
.
id: string
id
)) ??
function notFoundError(message?: string, data?: ErrorData): HttpError
notFoundError
(),
})

defineRoute()

function defineRoute(config: RouteConfig): void

Declares a route on the global router. router.defineRoute() and group.defineRoute() take the same configuration.

OptionTypeDefaultDescription
pathstringrequiredStarts with /. Static segments and :param segments only.
methodHttpMethod'get'get, post, put, patch, delete, head or options.
handler(ctx) => resultrequiredReceives the context, returns the response: a value, a response helper or an HttpError. Can be async.
schemaRouteSchema | (z) => RouteSchema—Zod schemas: see Schema. Declare it before handler, so the handler is typed from it.
middlewaresMiddleware[][]Run after the global and group middlewares.
statusnumber200Status of a success. 204 when the handler returns undefined.
metaRouteMeta{}Documentation: see Metadata.
bodyLimitnumberthe adapter limitMaximum body size, in bytes.
rawBodybooleanfalseKeeps the raw bytes of the body in req.rawBody, typed Uint8Array.

Paths

Pathreq.params
/articles{}
/articles/:id{ id: string }
/orgs/:orgId/members/:id{ orgId: string; id: string }

Parameter names match /^[A-Za-z_][A-Za-z0-9_]*$/. Wildcards, optional parameters and regular expressions are rejected. Matching is case-sensitive, ignores trailing slashes, and prefers static segments: see Paths.

Schema

interface RouteSchema {
  params?: Schema
  query?: Schema
  body?: Schema
  files?: Schema
  response?: Schema
}

Any Zod 4 schema, classic or mini. The request is validated after the middlewares, and before the handler: req.params, req.query, req.body and req.files hold the parsed values, typed by the output of the schemas. An invalid request gets a 400 VALIDATION_FAILED, with every issue. The response is parsed with schema.response (unknown keys of objects are stripped); a value that does not match is a 500, logged. See Validation.

arrayOf(schema) validates a field sent once or several times (query string, form fields, files) as an array:

schema: (z) => ({ query: z.object({ tag: arrayOf(z.string()).default([]) }) })

Metadata

FieldTypeDescription
summary, descriptionstringShown in the documentation.
tagsstring[]Groups the routes in the documentation.
operationIdstringThe OpenAPI operation ID.
deprecatedbooleanMarks the route as deprecated.
securitystring[]Security schemes accepted, any of them. Defaults to those of the middlewares; [] marks the route as public.
hiddenbooleanLeaves the route out of the documentation.
responsesRecord<number, string | { description, schema?, contentType? }>Other responses: returned errors, or responses that are not JSON.

meta is used by @dotpress/openapi, and available to plugins in app.routes.

createRouteGroup()

function createRouteGroup(prefix: string, middlewares?: Middleware[]): RouteGroup

Creates a group on the global router. router.createGroup() and group.createGroup() take the same arguments.

MemberDescription
defineRoute(config)Declares a route under the prefix, after the middlewares of the group.
createGroup(prefix, middlewares?)A nested group: prefixes and middlewares add up.
prefixThe prefix of the group.

Parameters of the prefix (/tenants/:tenant) are typed in the routes of the group, and so are the context properties guaranteed by its middlewares. See Route groups.

createRouter()

function createRouter(): Router

An isolated registry of routes, mounted with createApp({ routers }).

MemberDescription
defineRoute(config)Declares a route.
createGroup(prefix, middlewares?)Creates a group.
addResponseFilter(filter)A filter applied to the JSON results of the routes of this router.
getRoutes()The declared routes.
getResponseFilters()The registered filters.
clear()Removes every route and filter.

globalRouter is the router of defineRoute(), createRouteGroup() and registerResponseFilter(), mounted when createApp() has no routers.

defineMiddleware()

function defineMiddleware<TProvides>(
  middleware: (ctx: RequestContext) => MiddlewareResult | Promise<MiddlewareResult>,
  options?: { security?: string[] }
): Middleware<TProvides>

A middleware returns nothing to continue, or an HttpError or a response to stop the request. Throwing an HttpError works too.

ParameterDescription
TProvidesThe context properties the middleware guarantees: defineMiddleware<{ tenant: Tenant }> types ctx.tenant as Tenant in the handlers of its routes. The middleware must set them, or return an error.
options.securitySecurity schemes of the middleware (['bearerAuth']): its routes are documented as secured by them.

A plain function (ctx) => … is a middleware too, without guarantees. Middlewares run before validation: ctx.req holds the raw values. See Middlewares.

Response filters

type ResponseFilter = (ctx: RequestContext, result: unknown) => unknown | Promise<unknown>

A filter transforms the JSON result of a success, after the response validation. Filters run in this order: those of createApp({ responseFilters }), of the plugins, then of the router (router.addResponseFilter(), or registerResponseFilter() for the global router). Errors and other response helpers do not go through filters. See Response filters.