# Middleware Overview

Koa-style onion model middleware for intercepting, transforming, and controlling every request in Courier.

## How Middleware Works

Courier uses a **Koa-style onion model** for middleware. Each middleware wraps around the next one (and ultimately the endpoint handler), giving you full control over both the request and response phases of every call.

```
Request  ──▶  middleware A (before)  ──▶  middleware B (before)  ──▶  endpoint handler
Response ◀──  middleware A (after)   ◀──  middleware B (after)   ◀──  response
```

The first middleware in the array is the **outermost** layer: it runs first on the way in and last on the way out.

## Core Types

A middleware is a name, a phase and a handler. Every handler receives a `MiddlewareContext`:

```ts title="types"
type MiddlewareContext = {
  /** Source name (e.g., "api") */
  source: string
  /** Group name (e.g., "users") */
  group?: string
  /** Endpoint method name (e.g., "getAll") */
  method: string
  /** Request variables (body/params) — can be modified by middleware */
  variables?: unknown
  /** Request headers — can be modified by middleware */
  headers: Headers
  /** "http" for requests, "sse" or "ws" for streams and channels */
  transport?: "http" | "sse" | "ws"
  /** The incoming request, before `allowedHeaders` narrowed it, when a proxy runs the call */
  request?: Request
  /** Whether the endpoint reads or writes */
  kind?: "query" | "mutation"
  /** Whether the call carries the user's session: its answer is that user's alone */
  carriesSession?: boolean
  /** What tells one user's calls from the next in a browser or an app, which send the session's cookies themselves */
  sessionPartition?: string
  /** Fires when the caller stops waiting: the call's own signal, or the incoming request's in a proxy */
  signal?: AbortSignal
  /** Set on a request the session makes rather than an endpoint's call: "csrf-token" for the double-submit token a write asks for */
  purpose?: "csrf-token"
  /** The agent the call comes from, when an MCP server made it: its tool, access, principal and client */
  agent?: AgentCall
}

type MiddlewareResponse = {
  status: number
  data: unknown
  headers: Headers
}

type NextFn = () => Promise<MiddlewareResponse>

/** Runs before the call: can change the context, never sees the response */
type BeforeHandler = (ctx: MiddlewareContext) => void | Promise<void>

/** Runs after the call: receives the response and returns it, changed or not */
type AfterHandler = (
  ctx: MiddlewareContext,
  response: MiddlewareResponse,
) => MiddlewareResponse | Promise<MiddlewareResponse>

/** Wraps the call: decides whether, and how often, to call `next()` */
type WrapHandler = (ctx: MiddlewareContext, next: NextFn) => Promise<MiddlewareResponse>
```

## defineMiddleware

`defineMiddleware(name, phase, handler)` builds a middleware. The name is what Studio shows for it in each request's middleware timeline; the phase says what the handler receives:

```ts title="lib/middleware.ts"
import { defineMiddleware } from "@noverax/courier/middleware"

/* "before": change the request, then let it go on */
const auth = defineMiddleware("auth", "before", (ctx) => {
  ctx.headers.set("Authorization", "Bearer my-token")
})

/* "after": read or change the response on its way back */
const stamp = defineMiddleware("stamp", "after", (_ctx, response) => {
  response.headers.set("x-received-at", new Date().toISOString())
  return response
})

/* "wrap": both, around a call to next() */
const timing = defineMiddleware("timing", "wrap", async (ctx, next) => {
  const start = performance.now()
  const response = await next()
  const ms = Math.round(performance.now() - start)

  console.log(`${ctx.source}:${ctx.group}:${ctx.method} took ${ms}ms`)
  return response
})
```

Use `before` or `after` when a middleware only needs one side of the call — they cannot forget to call `next()` — and `wrap` for timing, retries, short-circuiting and error handling.

## composeMiddleware

`composeMiddleware` combines an array of middleware into one wrap handler, `(ctx, next) => Promise<MiddlewareResponse>`; `defineMiddleware(name, "wrap", …)` makes it a middleware of its own. Middleware execute in array order — the first element is the outermost layer:

```ts title="lib/middleware.ts"
import { composeMiddleware, defineMiddleware } from "@noverax/courier/middleware"

const auth = defineMiddleware("auth", "before", (ctx) => {
  ctx.headers.set("Authorization", "Bearer my-token")
})

const logger = defineMiddleware("logger", "wrap", async (ctx, next) => {
  console.log(`→ ${ctx.method}`)
  const res = await next()
  console.log(`← ${res.status}`)
  return res
})

// auth runs first (outermost), logger runs second (innermost)
const combined = composeMiddleware([auth, logger])
```

## Adding Middleware to defineApi

Pass a `middleware` array in your `defineApi` configuration. The middleware wraps `executeRequest` and applies to every endpoint in that source:

```ts title="lib/sources.ts"
import { courier, defineApi, defineGroup } from "@noverax/courier/define"
import {
  authMiddleware,
  logMiddleware,
  retryMiddleware,
} from "@noverax/courier/middleware"

const { endpoint } = courier()

const api = defineApi({
  baseURL: "https://api.example.com",
  middleware: [
    logMiddleware(),
    retryMiddleware({ limit: 3, delay: 300 }),
    authMiddleware({ getToken: () => getAccessToken() }),
  ],
  groups: {
    users: defineGroup({
      prefix: "users",
      endpoints: {
        getAll: endpoint.get("").response<User[]>(),
        create: endpoint.post("").variables<CreateUserInput>().response<User>(),
      },
    }),
  },
})
```

> **Execution order matters**
>
> Middleware execute in array order. In the example above, `logMiddleware` is outermost (logs first, receives the final response last), `authMiddleware` is innermost (runs right before the endpoint handler). Place cross-cutting concerns like logging and retry at the beginning, and request-specific concerns like auth at the end.

## Key Capabilities

Middleware can do any of the following:

| Capability | How |
|---|---|
| **Modify request headers** | A `before` handler sets values on `ctx.headers` |
| **Modify variables** | A `before` handler reassigns `ctx.variables` |
| **Transform response** | An `after` handler returns a changed `MiddlewareResponse` |
| **Short-circuit** | A `wrap` handler returns a `MiddlewareResponse` without calling `next()` |
| **Retry** | A `wrap` handler calls `next()` more than once |
| **Measure timing** | A `wrap` handler records `performance.now()` before and after `next()` |
| **Handle errors** | A `wrap` handler puts `next()` in try/catch |

## Next Steps

- [Built-in Middleware](/docs/middleware/built-in) -- ready-to-use middleware for retry, auth, logging, rate limiting, and caching
- [Custom Middleware](/docs/middleware/custom) -- how to write your own middleware from scratch
