Routing
DaloyJS uses a trie/radix router with a static-route fast path. Static routes resolve via a single Map.get; dynamic routes walk a trie in O(path-segments) regardless of how many routes you have.
Defining routes
A route declaration is the source of truth for matching, request validation, response validation, OpenAPI output, and typed clients. Provide an operationId for every public route you want in the typed client or generated SDK; DaloyJS rejects duplicate operationId values at registration.
Type inference and chaining
app.route() returns the same app instance with a widened route tuple type. Chain route registrations when you want createClient(app) to expose strongly typed methods for each operationId. Separate statements still register routes at runtime and in OpenAPI, but TypeScript cannot widen the already-created app variable.
HTTP methods
Supported methods include GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. Custom methods such as TRACE, CONNECT, and WebDAV verbs are rejected at registration.
HEAD falls back to the matching GET route when no explicit HEAD route exists, returning the same headers with an empty body. OPTIONS returns a 204 preflight with an Allow header when a path exists but no explicit OPTIONS route is registered.
Path parameters
Path values are decoded before validation. If you omit a request.params schema, ctx.params is inferred from the path as raw strings. Conflicting parameter names at the same trie position, such as /a/:x and /a/:y, throw at registration.
Wildcard captures
A trailing *name segment captures the rest of the path into one decoded string. Wildcards must be terminal.
Path traversal segments (..), empty segments //, and malformed percent escapes miss cleanly before your handler sees them.
Groups
Groups merge prefixes, tags, hooks, and auth defaults into the routes registered inside the callback. The child app is encapsulated: middleware added inside a group does not leak to routes outside that group. Grouped routes are visible to runtime routing and OpenAPI.
Route options
request: schemas forparams,query,headers, andbody.responses: declared status codes and optional response body/header schemas.accepts: per-routeContent-Typeallowlist for routes with request body schemas.auth: OpenAPI security requirement for the route; pair it with an auth hook such asbearerAuth().internal: hides a route from public adapters while still allowing in-processapp.inject()calls.deprecatedandsunset: mark an endpoint as deprecated and emit the matching response headers.callbacksandmeta: add OpenAPI callbacks, examples, and AI-friendly route metadata.
Hooks
Hooks attach behavior at fixed lifecycle points:
onRequest: earliest, before parsing.beforeHandle: after validation, before your handler. Return aResponseto short-circuit.afterHandle: wrap or transform the handler result before response serialization.onError: observe or replace the error response.onSend: mutate outgoing headers in place or return a newResponse. Runs on success, error, andOPTIONSpreflight paths.onResponse: final observer. Use it for logging and metrics, not response mutation.
- 01earliestonRequestbefore parsing
- 02frameworkvalidateparams · query · body · headers
- 03beforeHandlereturn a Response to short-circuit
- 04handleryour route logic
- 05afterHandlewrap / transform the result
- 06onSendmutate or replace the Response
- 07alwaysonResponseobservability only
Transforming responses with onSend
Use onSend when you need to rewrite the outgoing response, for example, to attach a header, strip an internal header, or replace the response entirely. Returning void keeps the current response. Multiple onSend hooks compose pipeline-style (global → group → route).
onSend runs after response validation and after request-scoped headers, including x-request-id, have been merged. It runs before onResponse, which remains the right place for logging and metrics.
405 Method Not Allowed
If a path is registered for one method but called with another, the router returns 405 with a correct Allow header, never a misleading 404. Routes marked internal: true are filtered from public 405 and Allow responses so hidden admin or cron endpoints do not leak through method probing.