> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# File-Based Routing: Map Files to HTTP Endpoints in Anvil

> How Anvil maps server/routes/ to HTTP endpoints: one file per HTTP verb, auto-serialization, route groups, and the build-time static manifest.

Anvil turns your directory structure into an HTTP API. Place a file named after an HTTP verb inside `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) |

Every file must export a default function (the handler). Any other named export is metadata — `meta`, `paramsSchema`, etc.

## Full routing tree example

```text theme={null}
server/routes/
  get.ts                      → GET /
  users/
    _middleware.ts            → scoped middleware for /users/**
    get.ts                    → GET /users
    post.ts                   → POST /users
    [id]/
      get.ts                  → GET /users/:id
  files/
    [...path]/
      get.ts                  → GET /files/* (catch-all)
  (admin)/                    → route group — not part of the URL
    dashboard/
      get.ts                  → GET /dashboard
```

## Writing a handler

A handler is any function that accepts a `Context` and returns a value. Import `Context` from `anvil` for full TypeScript types:

```ts server/routes/get.ts theme={null}
import type { Context } from 'anvil-sdk';

export default function handler(ctx: Context) {
  return {
    name: 'my-api',
    version: '1.0.0',
  };
}
```

Handlers can be synchronous or async:

```ts server/routes/users/post.ts theme={null}
import { HttpError, type Context } from 'anvil-sdk';

export default async function handler(ctx: Context) {
  const body = await ctx.body<{ name?: string }>();
  if (typeof body !== 'object' || body === null || !body.name) {
    throw new HttpError(400, 'Expected JSON body with a "name" field');
  }
  return ctx.json({ created: { id: Date.now().toString(36), name: body.name } }, { status: 201 });
}
```

## Auto-serialization

You don't need to call `ctx.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        |

This means the simplest possible handler is just a function that returns data:

```ts server/routes/users/get.ts theme={null}
export default function handler() {
  return { users: [{ id: '1', name: 'Alice' }] };
}
```

## 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.

```text theme={null}
server/routes/
  (admin)/
    dashboard/
      get.ts        → GET /dashboard   (not GET /(admin)/dashboard)
    settings/
      get.ts        → GET /settings
```

Route groups are useful for:

* Applying a `_middleware.ts` to 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](/routing/middleware) for the full guide.

## The route manifest

Running `anvil 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.

```ts .gen/routes.ts (generated — do not edit) theme={null}
// Generated by `anvil build`. Do not edit.
import type { Manifest, Middleware, RouteMeta } from 'anvil-sdk';
import * as r0 from '../server/routes/get.js';
import * as r1 from '../server/routes/users/get.js';
// ...

export const manifest: Manifest = {
  routes: [
    {
      method: 'GET',
      pattern: '/',
      segments: [],
      handler: r0.default,
      middleware: [],
      meta: metaOf(r0),
      file: '../server/routes/get.js',
    },
    // ...
  ],
};
```

<Note>
  Never edit `.gen/routes.ts` directly. It is regenerated every time you run `anvil build` or `anvil dev` detects a change.
</Note>

## Compile-time validation

Run `anvil 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 your `paramsSchema` has `{ 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/` and `users/` are the same on Windows but different on Linux; lint catches this so your app behaves the same on both.

```bash theme={null}
anvil lint          # warns on issues
anvil lint --strict # exits non-zero on warnings too
```
