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/concepts/context.md.

Request context

Handlers and middlewares receive one argument: the context of the request, ctx. Destructure what you need:

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

Declares a route on the global router.

defineRoute
({
RouteConfig<"/reports/:id", EmptyObject, EmptyObject, false, unknown[], unknown>.path: "/reports/:id"
path
: '/reports/:id',
RouteConfig<"/reports/:id", EmptyObject, EmptyObject, false, unknown[], unknown>.handler: RouteHandler<EmptyObject, {
    id: string;
}, false, unknown>
handler
: ({
req: RouteRequest<EmptyObject, {
    id: string;
}, false>
req
,
res: DotpressResponse
res
,
logger: Logger

Logger bound to the request (requestId field).

logger
,
requestId: string

Unique identifier of the request, also sent as X-Request-ID.

requestId
}) => {
logger: Logger

Logger bound to the request (requestId field).

logger
.
Logger.info: LogFn
(data: object, message?: string, ...args: unknown[]) => void (+1 overload)
info
({
reportId: string
reportId
:
req: RouteRequest<EmptyObject, {
    id: string;
}, false>
req
.
DotpressRequest<{ id: string; }, QueryValues, unknown, UploadedFiles>.params: {
    id: string;
}
params
.
id: string
id
}, 'Report requested')
res: DotpressResponse
res
.
DotpressResponse.setHeader(name: string, value: string | string[]): DotpressResponse
setHeader
('cache-control', 'no-store')
return {
id: string
id
:
req: RouteRequest<EmptyObject, {
    id: string;
}, false>
req
.
DotpressRequest<{ id: string; }, QueryValues, unknown, UploadedFiles>.params: {
    id: string;
}
params
.
id: string
id
,
requestId: string
requestId
}
}, })

What it holds

KeyContent
reqThe request: method, path, url, typed params, query, body and files, headers (lowercase names), cookies, ip, signal, header(name)
resResponse metadata: setStatus(), setHeader(), appendHeader(), removeHeader(), setCookie(), clearCookie()
requestIdThe ID of the request, also sent in the X-Request-ID header
loggerA logger bound to the request: every line carries the request ID
rawThe native request of the adapter (Express, Fastify, Hono, Lambda event), as an escape hatch
publicPath(path)The path of a route as seen by the browser, behind a proxy (see base path)

req and res are the same on every adapter: a handler never touches the native request or response. It returns its value, and sets the status, headers and cookies on res.

req.signal is aborted when the client disconnects: pass it to long operations (fetch(url, { signal: req.signal })).

Keys added by plugins

Plugins add their own keys to the context, typed:

KeyPlugin
ctx.user, ctx.auth@dotpress/auth: the authenticated user and their token
ctx.memo, ctx.cache@dotpress/cache
ctx.events@dotpress/events
ctx.mailer@dotpress/mailer

Keys provided with decorateContext() are computed on first access, at most once per request: a route that does not use the mailer does not pay for it.

Using the key of a plugin that is not installed throws an explicit error, rather than returning undefined:

"ctx.mailer" is used, but the "mailerPlugin" plugin is not installed in createApp().

Adding your own keys

The context is extended with TypeScript module augmentation. A plugin provides the value, as in this plugin that exposes the services of the app:

import { 
const declareContextKey: (key: DecoratableContextKey, pluginName: string) => void

Declares a context key provided by a plugin, so that accessing it without the plugin installed throws an explicit error instead of returning undefined. Call it next to the module augmentation, at the top level of the package.

@exampledeclare module '@dotpress/core' { interface RequestContext { mailer: Mailer } } declareContextKey('mailer', 'mailerPlugin')
declareContextKey
,
const definePlugin: <const T extends Plugin>(plugin: T) => T

Declares a plugin. Extra properties are allowed, and exposed to other plugins through api.getPlugin().

@exampleexport const healthPlugin = (path = '/health') => definePlugin({ name: 'health', setup(api) { api.addRoute({ path, handler: () => ({ status: 'ok' }) }) }, })
definePlugin
} from '@dotpress/core'
interface AppServices {
AppServices.tasks: {
    findById(id: string): Promise<{
        id: string;
    } | undefined>;
}
tasks
: {
function findById(id: string): Promise<{
    id: string;
} | undefined>
findById
(
id: string
id
: string):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<{
id: string
id
: string } | undefined> }
} declare module '@dotpress/core' { interface RequestContext {
RequestContext.services: AppServices
services
: AppServices
} }
function declareContextKey(key: DecoratableContextKey, pluginName: string): void

Declares a context key provided by a plugin, so that accessing it without the plugin installed throws an explicit error instead of returning undefined. Call it next to the module augmentation, at the top level of the package.

@exampledeclare module '@dotpress/core' { interface RequestContext { mailer: Mailer } } declareContextKey('mailer', 'mailerPlugin')
declareContextKey
('services', 'servicesPlugin')
export const
const servicesPlugin: (services: AppServices) => {
    readonly name: "services";
    readonly setup: (api: PluginAPI) => void;
}
servicesPlugin
= (
services: AppServices
services
: AppServices) =>
definePlugin<{
    readonly name: "services";
    readonly setup: (api: PluginAPI) => void;
}>(plugin: {
    readonly name: "services";
    readonly setup: (api: PluginAPI) => void;
}): {
    readonly name: "services";
    readonly setup: (api: PluginAPI) => void;
}

Declares a plugin. Extra properties are allowed, and exposed to other plugins through api.getPlugin().

@exampleexport const healthPlugin = (path = '/health') => definePlugin({ name: 'health', setup(api) { api.addRoute({ path, handler: () => ({ status: 'ok' }) }) }, })
definePlugin
({
name: "services"
name
: 'services',
function setup(api: PluginAPI): void
setup
(
api: PluginAPI
api
) {
api: PluginAPI
api
.
PluginAPI.decorateContext<"services">(key: "services", factory: ContextFactory<"services">): void

Provides a context key, declared on RequestContext through module augmentation. The factory runs on first access, once per request.

@exampledeclare module '@dotpress/core' { interface RequestContext { mailer: Mailer } } api.decorateContext('mailer', () => mailer)
decorateContext
('services', () =>
services: AppServices
services
)
}, })

A middleware can also set a key for the request, such as the tenant of a multi-tenant API. Declare it optional, and let a typed middleware guarantee it to the handlers.

See Project structure for the pattern in a complete app.