Runtime Behavior
Routa v0 runs on Hono. This page documents the request pipeline, every status Routa
produces on its own, and the shape of its error responses. Behavior described here comes
from createHonoApp and applies to routa dev, routa start, and any app you build
directly.
Request Pipeline
Section titled “Request Pipeline”For each matched route, in order:
- Accept negotiation. A request whose
Acceptheader cannot be satisfied by JSON receives406 Not Acceptable. A missingAcceptheader is treated as acceptable. - Input parsing. Declared
params,query,headers,cookies, andbodyare parsed with their Zod schemas. The body is read at most once per request and shared between middleware and the handler. - Context creation.
createContext()runs if provided. It must return a plain object. - Middleware chain. Each middleware’s
requireskeys are checked against the current context, thenrunexecutes. It either returns a reject outcome, ending the request, or callsnext(ctx)to continue. Declaredprovidesvalues are parsed before any are merged, and undeclared top-level keys are omitted. - Handler.
runreceives the parsed input and the accumulated context, with framework-owned values such asloggerapplied last so they cannot be replaced. - Response validation. The returned result shape and
typeare checked against the route’sresponsesand every middlewarerejectsmap. Itsdatais parsed according to the configured response-validation policy. - Serialization. The response is written as
application/json; charset=utf-8, with lifecycle headers when enabled.
Status Codes Routa Produces
Section titled “Status Codes Routa Produces”| Status | Cause |
|---|---|
204 |
Automatic OPTIONS response |
400 |
Input failed schema validation, or the JSON body could not be parsed |
405 |
The path exists but does not declare the requested method |
406 |
The Accept header cannot be satisfied by JSON |
415 |
The request body’s content type is not a JSON media type |
500 |
A handler or middleware returned output that does not match its contract |
Every other status comes from your contract.
Error Response Shape
Section titled “Error Response Shape”For most errors Routa generates, it returns problem documents. Responses with status 405 or 406 are the plain-text exceptions:
{ "type": "https://routa-ts.dev/problems/validation", "title": "Validation failed", "status": 400, "issues": [{ "path": ["body", "email"], "message": "Invalid email address" }]}type |
Title | Status |
|---|---|---|
…/problems/validation |
Validation failed | 400 |
…/problems/invalid-json |
Invalid JSON body | 400 |
| Plain text (no problem type) | Method Not Allowed | 405 |
| Plain text (no problem type) | Not Acceptable | 406 |
…/problems/unsupported-media-type |
Unsupported Media Type | 415 |
…/problems/handler-output |
Invalid handler output | 500 |
…/problems/internal |
Internal Server Error | 500 |
Only input validation problems include issues in the response. Handler-output problems
never leak the underlying message or response values, since they may contain internal
data.
Methods
Section titled “Methods”Applications declare get, post, put, patch, delete, and optionally head.
OPTIONSis automatic. Every known path answersOPTIONSwith204and anAllowheader derived from that path’s methods, including the implicitHEADforGETandOPTIONSitself. Declaringoptionsis a registration error.HEADis implicit forGET. Declareheadexplicitly only when it needs its own contract; explicitHEADcontracts are registered ahead ofGETso they win without intercepting realGETrequests.GETandHEADcannot declare a body. Registration throws.- Unsupported methods return
405with the sameAllowheader.
CORS Preflight
Section titled “CORS Preflight”A preflight carrying both Origin and Access-Control-Request-Method receives the derived
method list as Access-Control-Allow-Methods, plus
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers. Routa never
sets Access-Control-Allow-Origin, so cross-origin access still requires an explicit CORS
policy in your application.
Input Parsing Details
Section titled “Input Parsing Details”| Source | Behavior |
|---|---|
params |
Values from dynamic path segments |
query |
The query string as a flat record; repeated keys collapse to the last value |
headers |
All request headers, lowercased |
cookies |
Parsed from the Cookie header and URI-decoded, falling back to the raw value on malformed encoding |
body |
Requires application/json or a +json media type, otherwise 415 |
Response Serialization
Section titled “Response Serialization”Handlers and middleware rejects return { type, data }. Routa always looks up the type
and status from the matching response definition. With the default
responseValidation: "development", it parses data through that response schema in
routa dev and skips the parse in routa start. Set responseValidation: "always" to
parse in both modes. The CLI passes the mode explicitly; Routa does not infer it from
NODE_ENV.
When schema parsing is disabled, Zod transforms, defaults, and unknown-key stripping do
not run. JSON serialization still runs. Statuses 204, 205, and 304 are emitted with
no body, and data that cannot be serialized as JSON produces a handler-output problem
rather than a partial response. See Configuration for
the policy table.
Return the declared result object from handlers and middleware. A raw Response is not a
response escape hatch: returning one is invalid handler output, while throwing one is
handled as an internal error. Both produce a generic 500 problem response.
Logging
Section titled “Logging”When a logger is configured, createHonoApp records http.request at info for every
completed request with method, path, status, and durationMs, and http.error at
error for responses of 500 or above with the error message, name, and stack. Response
and middleware context schema failures also include structured issues containing only
each Zod issue’s code and path; rejected values are never logged.
Handlers always receive ctx.logger. With logging disabled it is a complete no-op
implementation of the same type.