Skip to content

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.

For each matched route, in order:

  1. Accept negotiation. A request whose Accept header cannot be satisfied by JSON receives 406 Not Acceptable. A missing Accept header is treated as acceptable.
  2. Input parsing. Declared params, query, headers, cookies, and body are parsed with their Zod schemas. The body is read at most once per request and shared between middleware and the handler.
  3. Context creation. createContext() runs if provided. It must return a plain object.
  4. Middleware chain. Each middleware’s requires keys are checked against the current context, then run executes. It either returns a reject outcome, ending the request, or calls next(ctx) to continue. Declared provides values are parsed before any are merged, and undeclared top-level keys are omitted.
  5. Handler. run receives the parsed input and the accumulated context, with framework-owned values such as logger applied last so they cannot be replaced.
  6. Response validation. The returned result shape and type are checked against the route’s responses and every middleware rejects map. Its data is parsed according to the configured response-validation policy.
  7. Serialization. The response is written as application/json; charset=utf-8, with lifecycle headers when enabled.
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.

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.

Applications declare get, post, put, patch, delete, and optionally head.

  • OPTIONS is automatic. Every known path answers OPTIONS with 204 and an Allow header derived from that path’s methods, including the implicit HEAD for GET and OPTIONS itself. Declaring options is a registration error.
  • HEAD is implicit for GET. Declare head explicitly only when it needs its own contract; explicit HEAD contracts are registered ahead of GET so they win without intercepting real GET requests.
  • GET and HEAD cannot declare a body. Registration throws.
  • Unsupported methods return 405 with the same Allow header.

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.

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

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.

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.