How Dotpress works
Dotpress separates what your API does from the server that runs it.
Routes are declarations
defineRoute() does not attach a function to a server: it records a declaration, made of a method, a path, schemas, middlewares and a handler. createApp() reads the declarations of its routers, checks them and compiles each route into a request pipeline.
Because the routes are data before they are code:
- conflicts are detected at startup. Two routes with the same method and path make
createApp()throw, instead of one silently shadowing the other; - the app can describe itself.
app.routeslists the mounted routes, and@dotpress/openapibuilds the OpenAPI document from the same declarations: the documentation cannot drift from the code; - the same routes run anywhere. Nothing in a route depends on the HTTP server.
Routers
A router holds route declarations. createRouter() creates one, and createApp({ routers }) mounts them:
defineRoute() and createRouteGroup(), imported on their own, use the global router, which createApp() mounts when routers is omitted. Both styles are equivalent; explicit routers suit larger apps (see Project structure).
The app
createApp() takes an adapter and the rest of the configuration, and returns the app:
Its lifecycle:
createApp()sets up the plugins, in the order of their dependencies, compiles the routes and runs theonInithooks of the plugins.- Still in
createApp(), the adapter mounts the routes on its server, then theonReadyhooks run. app.listen()starts the server. On AWS Lambda, the adapter receives the events instead.- Each request goes through the request pipeline.
app.close()stops the server, waits for the pending requests, and runs theonClosehooks of the plugins, in reverse order.
Adapters
An adapter connects the core to an HTTP server. On each request, it:
- converts the native request into a standard one: method, path, headers, cookies, and the body, parsed by Dotpress according to its content type;
- passes it to the core, which matches the route and runs its pipeline;
- converts the response back, including streams and Server-Sent Events.
Routing is done by Dotpress, not by the server. The rules are the same everywhere: matching is case-sensitive, static segments win over parameters whatever the declaration order, trailing slashes are ignored, and HEAD requests use the GET route. Every adapter passes the same conformance test suite.
Your server stays available for what Dotpress does not do: each adapter accepts native middlewares (Express helmet, a Fastify plugin…), and app.native() returns the server instance. See Adapters.
Plugins
A plugin extends the app from the outside: it adds routes, global middlewares, response filters and hooks, and can provide new keys of the request context, such as ctx.user for @dotpress/auth. The packages of Dotpress are plugins, and your app can have its own. See Plugins.