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/groups.md.

Route groups

A group shares a path prefix and middlewares between routes. It organizes an API by resource (/projects), by audience (/admin) or by version (/v2).

const 
const admin: RouteGroup<EmptyObject & {}, unknown>
admin
=
createRouteGroup<"/admin", readonly [unknown]>(prefix: "/admin", middlewares?: readonly [Middleware<unknown>] | undefined): RouteGroup<EmptyObject & {}, unknown>

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

createRouteGroup
('/admin', [
const requireAdmin: Middleware<unknown>
requireAdmin
])
const admin: RouteGroup<EmptyObject & {}, unknown>
admin
.
RouteGroup<EmptyObject & {}, unknown>.defineRoute<"/users", EmptyObject, false, unknown[]>(route: RouteConfig<"/users", EmptyObject, EmptyObject & {}, false, unknown[], unknown>): void
defineRoute
({
RouteConfig<"/users", EmptyObject, EmptyObject & {}, false, unknown[], unknown>.path: "/users"
path
: '/users',
RouteConfig<"/users", EmptyObject, EmptyObject & {}, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {}, false, unknown>
handler
: () => [{
id: string
id
: '1',
name: string
name
: 'Ada' }],
})

The route answers GET /admin/users, after requireAdmin.

Nested groups

A group creates sub-groups. The prefixes add up, and so do the middlewares, from the outermost group to the route:

const 
const admin: RouteGroup<EmptyObject & {}, unknown>
admin
=
createRouteGroup<"/admin", readonly [unknown]>(prefix: "/admin", middlewares?: readonly [Middleware<unknown>] | undefined): RouteGroup<EmptyObject & {}, unknown>

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

createRouteGroup
('/admin', [
const requireAdmin: Middleware<unknown>
requireAdmin
])
const
const billing: RouteGroup<EmptyObject & {} & {}, unknown>
billing
=
const admin: RouteGroup<EmptyObject & {}, unknown>
admin
.
RouteGroup<EmptyObject & {}, unknown>.createGroup<"/billing", readonly [unknown]>(prefix: "/billing", middlewares?: readonly [Middleware<unknown>] | undefined): RouteGroup<EmptyObject & {} & {}, unknown>

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
('/billing', [
const requireBillingAccess: Middleware<unknown>
requireBillingAccess
])
const billing: RouteGroup<EmptyObject & {} & {}, unknown>
billing
.
RouteGroup<EmptyObject & {} & {}, unknown>.defineRoute<"/invoices", EmptyObject, false, unknown[]>(route: RouteConfig<"/invoices", EmptyObject, EmptyObject & {} & {}, false, unknown[], unknown>): void
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<"/invoices", EmptyObject, EmptyObject & {} & {}, false, unknown[], unknown>.path: "/invoices"
path
: '/invoices',
RouteConfig<"/invoices", EmptyObject, EmptyObject & {} & {}, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {}, false, unknown>
handler
: () => ({
created: boolean
created
: true }),
})

POST /admin/billing/invoices runs requireAdmin, then requireBillingAccess, then the route middlewares.

Parameters in the prefix

A prefix can hold parameters. Every route of the group receives them, typed:

const 
const projects: RouteGroup<EmptyObject & {
    projectId: string;
}, unknown>
projects
=
createRouteGroup<"/projects/:projectId", unknown[]>(prefix: "/projects/:projectId", middlewares?: readonly Middleware<unknown>[] | undefined): RouteGroup<EmptyObject & {
    projectId: string;
}, unknown>

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

createRouteGroup
('/projects/:projectId')
const
const tasks: RouteGroup<EmptyObject & {
    projectId: string;
} & {}, unknown>
tasks
=
const projects: RouteGroup<EmptyObject & {
    projectId: string;
}, unknown>
projects
.
RouteGroup<EmptyObject & { projectId: string; }, unknown>.createGroup<"/tasks", unknown[]>(prefix: "/tasks", middlewares?: readonly Middleware<unknown>[] | undefined): RouteGroup<EmptyObject & {
    projectId: string;
} & {}, unknown>

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
('/tasks')
const tasks: RouteGroup<EmptyObject & {
    projectId: string;
} & {}, unknown>
tasks
.
RouteGroup<EmptyObject & { projectId: string; } & {}, unknown>.defineRoute<"/:taskId", EmptyObject, false, unknown[]>(route: RouteConfig<"/:taskId", EmptyObject, EmptyObject & {
    projectId: string;
} & {}, false, unknown[], unknown>): void
defineRoute
({
RouteConfig<"/:taskId", EmptyObject, EmptyObject & { projectId: string; } & {}, false, unknown[], unknown>.path: "/:taskId"
path
: '/:taskId',
RouteConfig<"/:taskId", EmptyObject, EmptyObject & { projectId: string; } & {}, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {
    projectId: string;
    taskId: string;
}, false, unknown>
handler
: ({
req: RouteRequest<EmptyObject, {
    projectId: string;
    taskId: string;
}, false>
req
}) =>
req: RouteRequest<EmptyObject, {
    projectId: string;
    taskId: string;
}, false>
req
.
DotpressRequest<{ projectId: string; taskId: string; }, QueryValues, unknown, UploadedFiles>.params: {
    projectId: string;
    taskId: string;
}
params
,
})

Hover req.params: { projectId: string; taskId: string }. A member check written once on the /projects/:projectId group protects every route below it.

Typed context

A group with a typed middleware passes its guarantee to all its routes, sub-groups included. With requireAuth on a group, ctx.user is defined in every route of the group:

const 
const account: RouteGroup<EmptyObject & {}, AuthenticatedContext>
account
=
createRouteGroup<"/account", readonly [AuthenticatedContext]>(prefix: "/account", middlewares?: readonly [Middleware<AuthenticatedContext>] | undefined): RouteGroup<EmptyObject & {}, AuthenticatedContext>

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

createRouteGroup
('/account', [
const requireAuth: Middleware<AuthenticatedContext>

Requires an authenticated user (401 UNAUTHORIZED otherwise). In the handlers of the routes using it, ctx.user and ctx.auth are defined.

requireAuth
])
const account: RouteGroup<EmptyObject & {}, AuthenticatedContext>
account
.
RouteGroup<EmptyObject & {}, AuthenticatedContext>.defineRoute<"/profile", EmptyObject, false, unknown[]>(route: RouteConfig<"/profile", EmptyObject, EmptyObject & {}, false, unknown[], AuthenticatedContext>): void
defineRoute
({
RouteConfig<"/profile", EmptyObject, EmptyObject & {}, false, unknown[], AuthenticatedContext>.path: "/profile"
path
: '/profile',
RouteConfig<"/profile", EmptyObject, EmptyObject & {}, false, unknown[], AuthenticatedContext>.handler: RouteHandler<EmptyObject, {}, false, AuthenticatedContext>
handler
: ({
user: AuthUser

Authenticated user (authPlugin). Use requireAuth to require it.

user
}) => ({
name: string
name
:
user: AuthUser

Authenticated user (authPlugin). Use requireAuth to require it.

user
.
AuthUser.name: string
name
}),
})

Groups of a router

createRouteGroup() creates a group on the global router. With an explicit router, use router.createGroup():

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

Creates an isolated route registry.

createRouter
()
const
const projects: RouteGroup<EmptyObject & {}, unknown>
projects
=
const projectsRouter: Router
projectsRouter
.
RouteGroup<EmptyObject, unknown>.createGroup<"/projects", unknown[]>(prefix: "/projects", middlewares?: readonly Middleware<unknown>[] | undefined): RouteGroup<EmptyObject & {}, unknown>

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
('/projects')
const projects: RouteGroup<EmptyObject & {}, unknown>
projects
.
RouteGroup<EmptyObject & {}, unknown>.defineRoute<"/", EmptyObject, false, unknown[]>(route: RouteConfig<"/", EmptyObject, EmptyObject & {}, false, unknown[], unknown>): void
defineRoute
({
RouteConfig<"/", EmptyObject, EmptyObject & {}, false, unknown[], unknown>.path: "/"
path
: '/',
RouteConfig<"/", EmptyObject, EmptyObject & {}, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {}, false, unknown>
handler
: () => [] })
const projects: RouteGroup<EmptyObject & {}, unknown>
projects
.
RouteGroup<EmptyObject & {}, unknown>.defineRoute<"/:projectId", EmptyObject, false, unknown[]>(route: RouteConfig<"/:projectId", EmptyObject, EmptyObject & {}, false, unknown[], unknown>): void
defineRoute
({
RouteConfig<"/:projectId", EmptyObject, EmptyObject & {}, false, unknown[], unknown>.path: "/:projectId"
path
: '/:projectId',
RouteConfig<"/:projectId", EmptyObject, EmptyObject & {}, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {
    projectId: string;
}, false, unknown>
handler
: ({
req: RouteRequest<EmptyObject, {
    projectId: string;
}, false>
req
}) =>
req: RouteRequest<EmptyObject, {
    projectId: string;
}, false>
req
.
DotpressRequest<{ projectId: string; }, QueryValues, unknown, UploadedFiles>.params: {
    projectId: string;
}
params
})

A route at path: '/' answers the prefix itself: GET /projects. Prefixes start with /: to share middlewares without a prefix, give them to each route, or use a global middleware.

See the Tasks API example for nested groups with access checks.