_middleware.ts file next to the routes it should affect — no central app.use() call, no manual import lists. Anvil wires it up automatically based on location.
The _middleware.ts convention
A file named _middleware.ts in any server/routes/ folder runs for every route under that folder. Root-level middleware (server/routes/_middleware.ts) runs for every request in the application, including unmatched paths (so static files and the observability dashboard can be served from there).
Root middleware example
The root_middleware.ts is a great place for logging, static file serving, and the built-in observability dashboard:
server/routes/_middleware.ts
Exporting an array of middleware functions is supported. Anvil flattens single functions and arrays — you can mix and match in the same file.
The onion (compose) model
Middleware wraps the handler in an “onion” pattern. Root middleware is the outermost layer; the middleware closest to the handler file runs innermost. Each function receivesctx and a next function to call the next layer.
next() passes control inward. You can run code before the handler (pre-processing) and after it (post-processing):
server/routes/users/_middleware.ts
Composing multiple middleware functions
To apply several middleware functions to the same scope, export them as an array from_middleware.ts. Anvil flattens the array and runs each function in order:
server/routes/users/_middleware.ts
compose() helper (exported from anvil) is used internally by the framework to build the full middleware chain at request time. You can also call it directly when you need to programmatically assemble a handler pipeline — for example, in tests or when creating reusable middleware factories:
Passing data with ctx.state
ctx.state is a mutable Record<string, unknown> on every request context. Middleware can write values to it; the downstream handler (or the next middleware) reads them. This replaces the pattern of mutating req in Express.
Auth guard example
server/routes/(authenticated)/_middleware.ts
ctx.state.user in any handler under the (authenticated)/ route group:
server/routes/(authenticated)/profile/get.ts
Short-circuiting
A middleware can return a response without callingnext() to stop the chain entirely. This is how auth guards, rate limiters, and maintenance-mode middleware work:
server/routes/_middleware.ts
Adding CORS
Anvil ships acors() middleware helper with the same options as the popular cors npm package. Add it to your root _middleware.ts:
server/routes/_middleware.ts
cors() options: