server/routes/ and Anvil registers the corresponding route — no import lists, no decorator registration, no central router config. The filesystem is your router.
The convention
Anvil recognizes these file names as HTTP verb handlers:| File name | HTTP method |
|---|---|
get.ts | GET |
post.ts | POST |
put.ts | PUT |
patch.ts | PATCH |
delete.ts | DELETE |
head.ts | HEAD |
options.ts | OPTIONS |
agent.ts | POST (streaming agent endpoint) |
meta, paramsSchema, etc.
Full routing tree example
Writing a handler
A handler is any function that accepts aContext and returns a value. Import Context from anvil for full TypeScript types:
server/routes/get.ts
server/routes/users/post.ts
Auto-serialization
You don’t need to callctx.json() for every response. Anvil normalizes whatever your handler returns:
| Return value | Response |
|---|---|
| Plain object or array | 200 application/json |
string | 200 text/plain; charset=utf-8 |
null or undefined | 204 No Content |
Response instance | Passed through unchanged |
server/routes/users/get.ts
Route groups
Wrap a folder name in parentheses to create a route group. The group name is stripped from the URL — it exists only to organize your files or scope middleware.- Applying a
_middleware.tsto a subset of routes without affecting their URLs - Organizing large route trees into logical sections (e.g.,
(public)/,(authenticated)/)
The _middleware.ts convention
A file named _middleware.ts in any route folder runs for all routes under that folder. Files starting with _ are never treated as route handlers — they are reserved for middleware, context, and other conventions.
See Scoped Middleware for the full guide.
The route manifest
Runninganvil build generates a static route manifest at .gen/routes.ts. This file statically imports every handler and middleware, so your production bundle has no runtime filesystem scanning — startup is instant regardless of how many routes you have.
.gen/routes.ts (generated — do not edit)
Never edit
.gen/routes.ts directly. It is regenerated every time you run anvil build or anvil dev detects a change.Compile-time validation
Runanvil lint before deploying to catch routing problems at build time rather than at runtime:
- Param schema keys must match the actual folder params — if your folder is
[id]/but yourparamsSchemahas{ userId: z.string() }, lint fails. - MCP-exposed schemas must convert losslessly to JSON Schema (e.g., no
z.transform()without a target type). - Case-insensitive collisions —
Users/andusers/are the same on Windows but different on Linux; lint catches this so your app behaves the same on both.