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

# Middleware in Anvil JS: Scoped _middleware.ts Files

> Use _middleware.ts files to run middleware for all routes under a folder. Compose multiple functions, pass data via ctx.state, and add CORS with cors().

Middleware in Anvil is code that runs before (and optionally after) your route handler. You place it in a `_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).

```text theme={null}
server/routes/
  _middleware.ts            ← runs for all routes + unmatched paths
  get.ts
  users/
    _middleware.ts          ← runs only for /users/**
    get.ts
    post.ts
    [id]/
      get.ts
```

### Root middleware example

The root `_middleware.ts` is a great place for logging, static file serving, and the built-in observability dashboard:

```ts server/routes/_middleware.ts theme={null}
import { serveStatic, type Middleware } from 'anvil-sdk';

const logger: Middleware = async (ctx, next) => {
  const start = Date.now();
  const res = await next();
  console.log(`${ctx.method} ${ctx.path} → ${res.status} (${Date.now() - start}ms)`);
  return res;
};

// Root middleware also runs for unmatched paths, so static files
// under public/ are served without needing a dedicated route.
export default [logger, serveStatic({ dir: 'public' })];
```

<Note>
  Exporting an **array** of middleware functions is supported. Anvil flattens single functions and arrays — you can mix and match in the same file.
</Note>

## 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 receives `ctx` and a `next` function to call the next layer.

```text theme={null}
Request
  └─ root _middleware (outermost)
       └─ /users _middleware
            └─ route handler (innermost)
       ← response flows back out through each layer
```

Calling `next()` passes control inward. You can run code before the handler (pre-processing) and after it (post-processing):

```ts server/routes/users/_middleware.ts theme={null}
import type { Middleware } from 'anvil-sdk';

const scope: Middleware = async (ctx, next) => {
  // Pre-processing: runs before the handler
  console.log(`Users scope: ${ctx.method} ${ctx.path}`);

  const res = await next(); // ← call the next layer

  // Post-processing: runs after the handler returns
  res.headers.set('x-scope', 'users');
  return res;
};

export default scope;
```

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

```ts server/routes/users/_middleware.ts theme={null}
import type { Middleware } from 'anvil-sdk';

const timer: Middleware = async (ctx, next) => {
  const start = Date.now();
  const res = await next();
  res.headers.set('x-response-time', `${Date.now() - start}ms`);
  return res;
};

const scopeHeader: Middleware = async (ctx, next) => {
  const res = await next();
  res.headers.set('x-scope', 'users');
  return res;
};

// Export an array — Anvil applies them outermost-first (timer wraps scopeHeader)
export default [timer, scopeHeader];
```

The `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:

```ts theme={null}
import { compose } from 'anvil-sdk';

// compose returns a (ctx: Context) => Promise<Response> — a fully-wired handler,
// not a Middleware. Use it when you want to call a pipeline directly, not as a
// _middleware.ts export.
const runChain = compose([timer, scopeHeader], myHandler);
const response = await runChain(ctx);
```

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

```ts server/routes/(authenticated)/_middleware.ts theme={null}
import { HttpError, type Middleware } from 'anvil-sdk';

const authGuard: Middleware = async (ctx, next) => {
  const token = ctx.headers.get('authorization')?.replace('Bearer ', '');

  if (!token) {
    throw new HttpError(401, 'Missing Authorization header');
  }

  // Validate the token — replace this with your real auth logic
  const user = await verifyToken(token);
  if (!user) {
    throw new HttpError(403, 'Invalid or expired token');
  }

  // Store the authenticated user so handlers can access it
  ctx.state.user = user;

  return next();
};

export default authGuard;
```

Read `ctx.state.user` in any handler under the `(authenticated)/` route group:

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

export default function handler(ctx: Context) {
  // ctx.state.user was set by the auth middleware
  const user = ctx.state.user as { id: string; name: string };
  return { profile: user };
}
```

## Short-circuiting

A middleware can return a response without calling `next()` to stop the chain entirely. This is how auth guards, rate limiters, and maintenance-mode middleware work:

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

const maintenanceMode: Middleware = async (ctx, next) => {
  if (process.env.MAINTENANCE === 'true') {
    // Returns immediately — the handler is never called
    return new Response(
      JSON.stringify({ error: 'Service temporarily unavailable', status: 503 }),
      { status: 503, headers: { 'content-type': 'application/json' } },
    );
  }
  return next();
};

export default maintenanceMode;
```

## Adding CORS

Anvil ships a `cors()` middleware helper with the same options as the popular `cors` npm package. Add it to your root `_middleware.ts`:

```ts server/routes/_middleware.ts theme={null}
import { cors, type Middleware } from 'anvil-sdk';

const logger: Middleware = async (ctx, next) => {
  const start = Date.now();
  const res = await next();
  console.log(`${ctx.method} ${ctx.path} → ${res.status} (${Date.now() - start}ms)`);
  return res;
};

export default [
  cors({
    origin: ['https://my-frontend.com', 'http://localhost:5173'],
    credentials: true,
  }),
  logger,
];
```

Available `cors()` options:

| Option           | Type       | Default                  | Description                                   |       |                                                                                 |
| ---------------- | ---------- | ------------------------ | --------------------------------------------- | ----- | ------------------------------------------------------------------------------- |
| `origin`         | \`'\*'     | string\[]                | (origin: string) => boolean\`                 | `'*'` | Allowed origins. Use an array for an allowlist or a predicate for custom logic. |
| `methods`        | `string[]` | All standard methods     | Allowed HTTP methods in preflight responses.  |       |                                                                                 |
| `allowedHeaders` | `string[]` | Reflects request headers | Allowed request headers.                      |       |                                                                                 |
| `exposedHeaders` | `string[]` | —                        | Headers the browser is allowed to read.       |       |                                                                                 |
| `credentials`    | `boolean`  | `false`                  | Set `Access-Control-Allow-Credentials: true`. |       |                                                                                 |
| `maxAge`         | `number`   | —                        | Preflight cache duration in seconds.          |       |                                                                                 |
