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

# The Anvil Context Object: Request and Response API

> Reference for the Context class: typed request properties, cached body parsing, response helpers, ctx.state for middleware data, and HttpError.

Every Anvil handler and middleware receives a single `Context` object. It is an immutable view of the incoming request plus a set of response-building helpers — Anvil's replacement for Express's mutation-heavy `(req, res)` pair. Import the type from `anvil`:

```ts theme={null}
import type { Context } from 'anvil';

export default async function handler(ctx: Context) {
  // ...
}
```

## Request properties

<ParamField path="ctx.req" type="Request">
  The underlying Web-standard `Request` object. Use this when you need access to APIs not exposed directly on `Context`, such as `ctx.req.arrayBuffer()` or `ctx.req.signal`.

  ```ts theme={null}
  export default async function handler(ctx: Context) {
    const rawBody = await ctx.req.arrayBuffer();
    // ...
  }
  ```
</ParamField>

<ParamField path="ctx.method" type="string">
  The HTTP method in uppercase, e.g. `"GET"`, `"POST"`, `"DELETE"`. Delegates to `ctx.req.method`.

  ```ts theme={null}
  export default function handler(ctx: Context) {
    console.log(ctx.method); // "GET"
  }
  ```
</ParamField>

<ParamField path="ctx.path" type="string">
  The URL pathname without the query string, e.g. `"/users/42"`. Equivalent to `new URL(ctx.req.url).pathname`.

  ```ts theme={null}
  export default function handler(ctx: Context) {
    console.log(ctx.path); // "/users/42"
  }
  ```
</ParamField>

<ParamField path="ctx.headers" type="Headers">
  The request headers as a Web-standard `Headers` object. Access individual headers with `.get()`.

  ```ts theme={null}
  export default function handler(ctx: Context) {
    const contentType = ctx.headers.get('content-type');
    const auth = ctx.headers.get('authorization');
  }
  ```
</ParamField>

<ParamField path="ctx.params" type="Record<string, string>">
  Named path parameters captured from the route pattern. The keys correspond to the dynamic folder names in your `server/routes/` tree (without the brackets).

  ```ts theme={null}
  // Route: server/routes/users/[id]/get.ts → GET /users/:id
  export default function handler(ctx: Context) {
    console.log(ctx.params.id); // "42" for GET /users/42
  }
  ```

  For catch-all routes (`[...param]`), `ctx.params.param` contains the full remaining path joined with `/`:

  ```ts theme={null}
  // Route: server/routes/files/[...path]/get.ts → GET /files/*
  export default function handler(ctx: Context) {
    console.log(ctx.params.path); // "images/2024/photo.png" for GET /files/images/2024/photo.png
  }
  ```
</ParamField>

<ParamField path="ctx.query" type="Record<string, string | string[]>">
  The URL query string parsed into a plain object. Repeated keys are collected into an array; single-occurrence keys are plain strings.

  ```ts theme={null}
  // GET /search?q=anvil&tag=node&tag=typescript
  export default function handler(ctx: Context) {
    console.log(ctx.query.q);    // "anvil"
    console.log(ctx.query.tag);  // ["node", "typescript"]
  }
  ```

  The result is computed lazily on first access and cached for the lifetime of the request.
</ParamField>

<ParamField path="ctx.state" type="Record<string, unknown>">
  A mutable scratch space for passing data from middleware to the handler (or to a later middleware). Anvil does not touch this object — it is entirely yours to use.

  ```ts theme={null}
  // In middleware:
  ctx.state.user = await verifyToken(token);

  // In the handler:
  const user = ctx.state.user as User;
  ```

  See [Scoped Middleware](/routing/middleware) for a full auth-guard example.
</ParamField>

## Body parsing

<ParamField path="ctx.body<T>()" type="() => Promise<T>">
  Parse the request body based on the `Content-Type` header:

  * `application/json` → parsed with `JSON.parse()`, returns the resulting value
  * `application/x-www-form-urlencoded` or `multipart/form-data` → returns a `FormData` object
  * Anything else → returns the body as a plain `string`

  The result is cached after the first call — calling `ctx.body()` from both a middleware and the handler is safe and reads the stream only once.

  ```ts theme={null}
  export default async function handler(ctx: Context) {
    const body = await ctx.body<{ name: string; email: string }>();
    // body is typed as { name: string; email: string }
    return { received: body };
  }
  ```

  If the body cannot be parsed (e.g., malformed JSON), Anvil throws an `HttpError(400, 'Malformed request body')` automatically.
</ParamField>

## Response helpers

All response helpers accept an optional `init` parameter of type `ResponseInit` (the same second argument as the Web `Response` constructor) for setting status codes and custom headers.

<ParamField path="ctx.json(data, init?)" type="(data: unknown, init?: ResponseInit) => Response">
  Serialize `data` to JSON and return a `Response` with `Content-Type: application/json`. Delegates to `Response.json()`.

  ```ts theme={null}
  return ctx.json({ id: '1', name: 'Alice' });
  // With a custom status:
  return ctx.json({ created: true }, { status: 201 });
  ```
</ParamField>

<ParamField path="ctx.text(data, init?)" type="(data: string, init?: ResponseInit) => Response">
  Return a `Response` with `Content-Type: text/plain; charset=utf-8`.

  ```ts theme={null}
  return ctx.text('Hello, world!');
  return ctx.text('Not found', { status: 404 });
  ```
</ParamField>

<ParamField path="ctx.html(data, init?)" type="(data: string, init?: ResponseInit) => Response">
  Return a `Response` with `Content-Type: text/html; charset=utf-8`.

  ```ts theme={null}
  return ctx.html('<h1>Hello</h1>');
  ```
</ParamField>

<ParamField path="ctx.redirect(location, status?)" type="(location: string, status?: number) => Response">
  Return a redirect response. `status` defaults to `302`. Use `301` for a permanent redirect.

  ```ts theme={null}
  return ctx.redirect('/users/profile');
  return ctx.redirect('https://example.com', 301);
  ```
</ParamField>

<ParamField path="ctx.stream(body, init?)" type="(body: ReadableStream, init?: ResponseInit) => Response">
  Return a streaming response. Pass a `ReadableStream` as the body — useful for server-sent events, large file transfers, and AI model output.

  ```ts theme={null}
  export default function handler(ctx: Context) {
    const stream = new ReadableStream({
      start(controller) {
        controller.enqueue(new TextEncoder().encode('data: hello\n\n'));
        controller.close();
      },
    });
    return ctx.stream(stream, {
      headers: { 'content-type': 'text/event-stream' },
    });
  }
  ```
</ParamField>

## Throwing HTTP errors

Use `HttpError` (imported from `anvil`) to throw an HTTP error from a handler or middleware. Anvil's kernel catches it and converts it to a structured JSON error response automatically.

```ts theme={null}
import { HttpError, type Context } from 'anvil';

export default async function handler(ctx: Context) {
  const token = ctx.headers.get('authorization');
  if (!token) throw new HttpError(401, 'Missing Authorization header');

  const body = await ctx.body<{ name?: string }>();
  if (!body || typeof body !== 'object' || !('name' in body)) {
    throw new HttpError(400, 'Expected a JSON body with a "name" field');
  }

  return { ok: true };
}
```

The JSON response looks like:

```json theme={null}
{
  "error": "Missing Authorization header",
  "status": 401
}
```

The full constructor signature is:

```ts theme={null}
new HttpError(status: number, message?: string, opts?: {
  details?: unknown;   // extra data included in the response body (4xx only by default)
  expose?: boolean;    // override the default expose logic (true = send message/details to client)
  cause?: unknown;     // the underlying error, forwarded as Error.cause for stack traces
})
```

<ResponseField name="status" type="number" required>
  The HTTP status code. 4xx errors expose their message to the client by default; 5xx errors do not (the client sees the generic status message instead).
</ResponseField>

<ResponseField name="message" type="string">
  The error message. For 4xx errors, this is sent to the client. For 5xx errors, it is hidden in production and only visible in dev mode (`NODE_ENV !== 'production'`).
</ResponseField>

<ResponseField name="opts.details" type="unknown">
  Optional additional data included in the `details` field of the JSON response. Only sent to the client when `expose` is true (the default for 4xx errors).
</ResponseField>

<ResponseField name="opts.expose" type="boolean">
  Override the default expose behaviour. Set to `true` to force the message and details onto a 5xx response in development, or `false` to hide them on a 4xx.
</ResponseField>

<ResponseField name="opts.cause" type="unknown">
  The underlying error that caused this `HttpError`. Forwarded as the standard `Error.cause` property — visible in stack traces but never sent to the client.
</ResponseField>

## Full handler example

Here is a handler that demonstrates multiple `Context` features together:

```ts server/routes/users/[id]/get.ts theme={null}
import { HttpError, type Context } from 'anvil';
import { z } from 'zod';

export const meta = { mcp: { expose: true, description: 'Fetch a user by ID' } };
export const paramsSchema = z.object({ id: z.string() });

const USERS = [
  { id: '1', name: 'Alice', role: 'admin' },
  { id: '2', name: 'Bob', role: 'user' },
];

export default function handler(ctx: Context) {
  // Read a dynamic route param
  const { id } = ctx.params;

  // Read an optional query parameter
  const format = ctx.query.format; // e.g. ?format=verbose

  // Read a request header
  const requestId = ctx.headers.get('x-request-id') ?? 'unknown';

  // Read authenticated user from middleware-set state
  const viewer = ctx.state.user as { role: string } | undefined;

  const user = USERS.find((u) => u.id === id);
  if (!user) throw new HttpError(404, `No user with id "${id}"`);

  // Restrict sensitive fields based on the viewer's role
  if (viewer?.role !== 'admin' && user.role === 'admin') {
    throw new HttpError(403, 'Forbidden');
  }

  return ctx.json(
    format === 'verbose' ? { user, requestId } : user,
  );
}
```
