Use this page as a checklist when designing route modules, middleware, and client usage. For the current server-boundary upgrade, use Migration from v5 to v6.
- Export route trees from small shared modules and import them from server and client code.
- Keep route declarations close to domain boundaries, not scattered through handler implementations.
- Use
http.resource(...)for resource namespaces and shared path params. - Name actions after domain operations such as
get,list,update,archive, andstream. - Let
http.get,http.post,http.put,http.patch, andhttp.deleteown the transport method. - Add Zod schemas when runtime guarantees matter; rely on inferred path params only when string params are enough.
- Use
$type<T>()for typed JSON success responses. - Use response maps with
$error<T>()for declared application errors that callers should handle as data. - Use
Responsereturns for redirects, binary payloads, custom non-JSON error bodies, or unusual headers. - Use
ndjson.$type<T>()only for response streams where each line is a JSON value. - Put shared auth, tracing, request IDs, environment bindings, and host-runtime checks in middleware before route registration.
$type<T>(),$error<T>(), andndjson.$type<T>()are compile-time type contracts. They do not re-validate handler return values.- Client action input is flat across path, query, and JSON body fields. Avoid duplicate field names across those schemas.
- Per-request
RequestInitfields belong in the second client action argument. Rouzer reservesmethod. - Raw-body actions with path or query input accept
bodyin the second argument. Raw-body actions without route input accept the body as the first argument. GETactions do not accept request bodies. Mutation actions do not accept query schemas.- The action API has no
ALLfallback route. Declare concrete actions for supported HTTP methods. - Pathname route patterns expect an absolute client
baseURL. - Resource and action keys are API names only. URL paths come from the pattern strings passed to resources and actions.
- Routes that use response plugin markers require matching router and client plugins.
- Declared
$error<T>()responses are JSON responses. Use a customResponsefor non-JSON error payloads. - Rouzer CORS support does not set
Access-Control-Allow-Credentials.
- Host data is under
ctx.host, includingctx.host.ipandctx.host.runtime. - The
env,runtime, andonResponserequest plugin keys are reserved. runtimeis a type-level marker forctx.host.runtime; it does not createctx.runtime.- Use
ctx.setHeaderfrom request middleware andresponse.headers.set(...)inside response callbacks. ctx.passThrough()skips the rest of the current chain. It is not an adapter pass-through mechanism.- Use
.isolate()when a chain should run without leaking context properties to the parent chain.
A common layout:
src/
routes/
profiles.ts
server/
middleware.ts
router.ts
client/
api.ts
routes/profiles.ts exports route contracts. server/router.ts imports those
contracts and attaches handlers. client/api.ts imports the same contracts and
creates the typed client.
Keep route modules free of server-only dependencies when browser clients import them.
Older Rouzer code used method-map routes. Current Rouzer uses action/resource route trees.
// Old shape
export const profileRoute = route('profiles/:id', {
GET: { response: $type<Profile>() },
PATCH: { body: updateProfileSchema, response: $type<Profile>() },
})Use named actions instead:
export const profiles = http.resource('profiles/:id', {
get: http.get({ response: $type<Profile>() }),
update: http.patch({
body: updateProfileSchema,
response: $type<Profile>(),
}),
})
export const routes = { profiles }Handler maps and clients mirror those action names.
createRouter().use(routes, {
profiles: {
get(ctx) {
return loadProfile(ctx.path.id)
},
update(ctx) {
return updateProfile(ctx.path.id, ctx.body)
},
},
})
await client.profiles.get({ id: '42' })
await client.profiles.update({ id: '42', name: 'Ada' })Use ctx.host.runtime for host runtime metadata and filterRuntime(...) for
runtime-specific branches. Keep runtime checks at the middleware boundary so
route handlers stay focused on request validation and application behavior.