Routes
A route is declared with defineRoute(): a method, a path and a handler. The value returned by the handler is the response.
The definition
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:
/Articlesdoes not match/articles. - Static segments first:
/articles/latestwins over/articles/:id, whatever the declaration order. - Trailing slashes ignored:
/articles/matches/articles. - Parameters are decoded:
/tags/c%23givesreq.params.tag === 'c#'. HEADuses theGETroute when there is noHEADroute, 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():
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:
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.
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'].