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/routing/routes.md.

Routes

A route is declared with defineRoute(): a method, a path and a handler. The value returned by the handler is the response.

defineRoute<"/articles", EmptyObject, false, unknown[]>(route: RouteConfig<"/articles", EmptyObject, EmptyObject, false, unknown[], unknown>): void

Declares a route on the global router.

defineRoute
({
RouteConfig<"/articles", EmptyObject, EmptyObject, false, unknown[], unknown>.path: "/articles"
path
: '/articles',
RouteConfig<"/articles", EmptyObject, EmptyObject, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {}, false, unknown>
handler
: () =>
const articles: {
    list(): Promise<{
        id: string;
        title: string;
    }[]>;
    create(input: {
        title: string;
    }): Promise<{
        id: string;
        title: string;
    }>;
}
articles
.
function list(): Promise<{
    id: string;
    title: string;
}[]>
list
(),
})
defineRoute<"/articles", {
    body: ZodObject<{
        title: ZodString;
    }, $strip>;
}, false, unknown[]>(route: RouteConfig<"/articles", {
    body: ZodObject<{
        title: ZodString;
    }, $strip>;
}, EmptyObject, false, unknown[], unknown>): void

Declares a route on the global router.

defineRoute
({
RouteConfig<P extends string, S extends RouteSchema, TParentParams = EmptyObject, TRawBody extends boolean = false, TProvided extends readonly unknown[] = unknown[], TGroupContext = unknown>.method?: HttpMethod | undefined

Defaults to get.

method
: 'post',
RouteConfig<"/articles", { body: ZodObject<{ title: ZodString; }, $strip>; }, EmptyObject, false, unknown[], unknown>.path: "/articles"
path
: '/articles',
RouteConfig<P extends string, S extends RouteSchema, TParentParams = EmptyObject, TRawBody extends boolean = false, TProvided extends readonly unknown[] = unknown[], TGroupContext = unknown>.status?: number | undefined

Success status. Defaults to 200 (204 when nothing is returned).

status
: 201,
RouteConfig<"/articles", { body: ZodObject<{ title: ZodString; }, $strip>; }, EmptyObject, false, unknown[], unknown>.schema?: SchemaDefinition<{
    body: ZodObject<{
        title: ZodString;
    }, $strip>;
}> | undefined
schema
: (
z: typeof import("zod")
z
) => ({
body: ZodObject<{
    title: ZodString;
}, $strip>
body
:
z: typeof import("zod")
z
.
function object<{
    title: ZodString;
}>(shape?: {
    title: ZodString;
} | undefined, params?: string | {
    error?: string | $ZodErrorMap<NonNullable<$ZodIssueInvalidType<unknown> | $ZodIssueUnrecognizedKeys>> | undefined;
    message?: string | undefined | undefined;
} | undefined): ZodObject<{
    title: ZodString;
}, $strip>
object
({
title: ZodString
title
:
z: typeof import("zod")
z
.
function string(params?: string | $ZodStringParams): ZodString (+1 overload)
string
().
_ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | $ZodCheckMinLengthParams): ZodString
min
(1) }) }),
RouteConfig<"/articles", { body: ZodObject<{ title: ZodString; }, $strip>; }, EmptyObject, false, unknown[], unknown>.handler: RouteHandler<{
    body: ZodObject<{
        title: ZodString;
    }, $strip>;
}, {}, false, unknown>
handler
: ({
req: RouteRequest<{
    body: ZodObject<{
        title: ZodString;
    }, $strip>;
}, {}, false>
req
}) =>
const articles: {
    list(): Promise<{
        id: string;
        title: string;
    }[]>;
    create(input: {
        title: string;
    }): Promise<{
        id: string;
        title: string;
    }>;
}
articles
.
function create(input: {
    title: string;
}): Promise<{
    id: string;
    title: string;
}>
create
(
req: RouteRequest<{
    body: ZodObject<{
        title: ZodString;
    }, $strip>;
}, {}, false>
req
.
DotpressRequest<{}, QueryValues, { title: string; }, UploadedFiles>.body: {
    title: string;
}
body
),
})

The definition

OptionDescription
pathThe path, starting with /. Parameters start with : (/articles/:id).
methodget (default), post, put, patch, delete, head or options.
handlerReceives the request context, returns the response. Can be async.
schemaZod schemas of the params, query, body, files and response: see Validation.
middlewaresMiddlewares of the route: see Middlewares.
statusSuccess status: 200 by default, 204 when the handler returns nothing.
metaDocumentation: summary, description, tags, other responses, security, hidden. Used by OpenAPI.
bodyLimitMaximum body size of this route, in bytes: see Bodies and file uploads.
rawBodyKeeps the raw bytes of the body in req.rawBody, to verify webhook signatures.

schema is a function that receives z, so that a route file does not even need to import Zod. A plain object works too: schema: { body: TaskInput }.

Paths

Paths contain static segments and :param segments only. Express wildcards, optional parameters and regular expressions are rejected at startup, so that a route behaves the same on every adapter.

The rules of matching are the same on every adapter:

  • Case-sensitive: /Articles does not match /articles.
  • Static segments first: /articles/latest wins over /articles/:id, whatever the declaration order.
  • Trailing slashes ignored: /articles/ matches /articles.
  • Parameters are decoded: /tags/c%23 gives req.params.tag === 'c#'.
  • HEAD uses the GET route when there is no HEAD route, and never sends a body.

Two routes with the same method and path, parameter names aside, make createApp() throw: a route can never shadow another silently.

When the path exists for other methods only, the response is 405 Method Not Allowed, with an Allow header listing them. Otherwise, 404 Not Found.

Routers

defineRoute() registers the route on the global router, which createApp() mounts when no routers are given. The modules declaring routes must be imported before createApp():

import './routes/articles.ts'
import './routes/authors.ts'

const app = await createApp({ adapter })

For a larger app, create explicit routers and give them to the app. Nothing is registered by importing a module, and a test can create an app with only some of the routers:

export const 
const articlesRouter: Router
articlesRouter
=
function createRouter(): Router

Creates an isolated route registry.

createRouter
()
const articlesRouter: Router
articlesRouter
.
RouteGroup<EmptyObject, unknown>.defineRoute<"/articles/:id", EmptyObject, false, unknown[]>(route: RouteConfig<"/articles/:id", EmptyObject, EmptyObject, false, unknown[], unknown>): void
defineRoute
({
RouteConfig<"/articles/:id", EmptyObject, EmptyObject, false, unknown[], unknown>.path: "/articles/:id"
path
: '/articles/:id',
RouteConfig<"/articles/:id", EmptyObject, EmptyObject, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {
    id: string;
}, false, unknown>
handler
: ({
req: RouteRequest<EmptyObject, {
    id: string;
}, false>
req
}) => ({
id: string
id
:
req: RouteRequest<EmptyObject, {
    id: string;
}, false>
req
.
DotpressRequest<{ id: string; }, QueryValues, unknown, UploadedFiles>.params: {
    id: string;
}
params
.
id: string
id
}),
})
const app = await createApp({ adapter, routers: [articlesRouter, authorsRouter] })

A router has the methods of a group, defineRoute() and createGroup(), plus addResponseFilter(). See Project structure.

Listing the routes

app.routes lists the mounted routes, with their method, full path, schemas, metadata and security: useful to check a configuration, or to generate a client.

Coming from v1

defineRoute() keeps the syntax of Dotpress 1. Handlers no longer receive the Express req and res: they return their value (see Responses), and path parameters are typed from the path, without params: ['id'].