# defineApi

Creates a data source with base URL, options, middleware, and endpoint groups.

## Import

```ts
import { defineApi } from "@noverax/courier/define"
```

## Signature

```ts
declare function defineApi<G extends OwnGroups>(
  options: BaseOptions & { groups: G }
): DefinedApi<G>
```

`groups` takes any name but `raw` or `with`, and the flat `endpoints` any name but `options`, `raw` or `with`: see [Reserved names](/docs/core-concepts/defining-apis#reserved-names). A reserved name is a type error and throws `ConfigurationError("RESERVED_ENDPOINT_NAME")` when the source is defined.

## Parameters

### BaseOptions

The core configuration for a data source:

```ts
type BaseOptions = {
  baseURL?: string
  responseType?: "json" | "text" | "blob" | "arrayBuffer" | "formData"
  responseParser?: (response: Response) => Promise<unknown>
  defaultBody?: Record<string, unknown>
  defaultQueryParams?: Record<string, unknown>
  allowedHeaders?: string[]
  bodySerializer?: (data: Record<string, unknown>) => BodyInit
  searchParamsSerializer?: SearchParamsSerializer
  coalesce?: boolean
  cancelPrevious?: readonly string[]
  middleware?: Middleware[]
  pathOptions?: PathOptions
  timeout?: number
  headers?: HeadersInit
  signal?: AbortSignal
  session?: SessionOptions
  forward?: ForwardOptions
}
```

## Basic Usage

### With Groups

```ts title="api/index.ts"
import { defineApi } from "@noverax/courier/define"
import { users } from "./users"
import { posts } from "./posts"

const api = defineApi({
  baseURL: process.env.API_URL ?? "http://localhost:4000",
  groups: { users, posts },
})

export const sources = { api }
export type Sources = typeof sources
```

### With Options

```ts title="api/index.ts"
import { defineApi } from "@noverax/courier/define"
import { logMiddleware, authMiddleware } from "@noverax/courier/middleware"

const api = defineApi({
  baseURL: process.env.API_URL ?? "http://localhost:4000",

  // Response parsing (default: "json")
  responseType: "json",

  // Default data merged into every POST/PUT/PATCH body
  defaultBody: { apiKey: process.env.API_KEY },

  // Default params merged into every request
  defaultQueryParams: { locale: "en" },

  // Request timeout in milliseconds
  timeout: 10_000,

  // Static headers
  headers: {
    "X-Client": "courier",
    Authorization: `Bearer ${process.env.API_SECRET}`,
  },

  // Middleware chain
  middleware: [
    logMiddleware(),
    authMiddleware({ getToken: () => getAuthToken() }),
  ],

  // A newer search cancels the one still in flight
  cancelPrevious: ["users.search"],

  groups: { users, posts },
})
```

## Options Reference

### baseURL

Sets the base URL prefix for all requests from this source.

### responseType

Controls response parsing. Supported: `"json"` (default), `"text"`, `"blob"`, `"arrayBuffer"`, `"formData"`. A `204` status always returns `null`. A proxy client resolves to the same kind of value as a direct call.

### responseParser

Custom response parser that overrides `responseType`:

```ts
const api = defineApi({
  baseURL: "...",
  responseParser: async (response) => {
    const text = await response.text()
    return new DOMParser().parseFromString(text, "text/xml")
  },
  groups: { users },
})
```

### defaultBody

Object merged into every POST/PUT/PATCH body:

```ts
const api = defineApi({
  baseURL: "...",
  defaultBody: { apiKey: "...", version: "v2" },
  groups: { users },
})
// POST /users body: { apiKey: "...", version: "v2", name: "Alice" }
```

### defaultQueryParams

Object merged into every request's search parameters:

```ts
const api = defineApi({
  baseURL: "...",
  defaultQueryParams: { locale: "en", format: "json" },
  groups: { users },
})
// GET /users → GET /users?locale=en&format=json
```

### middleware

Array of middleware functions applied to every request. See [Middleware](/docs/middleware/overview).

### bodySerializer

Custom body encoder (e.g., for URL-encoded forms):

```ts
const api = defineApi({
  baseURL: "...",
  bodySerializer: (data) =>
    new URLSearchParams(Object.entries(data).map(([k, v]) => [k, String(v)])),
  groups: { users },
})
```

### searchParamsSerializer

Custom search params encoder:

```ts
import qs from "qs"

const api = defineApi({
  baseURL: "...",
  searchParamsSerializer: {
    stringify: (obj) => qs.stringify(obj),
    parse: (str) => qs.parse(str),
  },
  groups: { users },
})
```

### coalesce

Identical reads in flight together share one request — the same method, address, headers and body, every credential among them — and each call gets a copy of the answer. On by default; `false` sends every call of the API alone, and a call can say so for itself with `.with({ coalesce: false })`. Writes are never shared. See [Deduplication & Abort](/docs/advanced/abort).

### cancelPrevious

The reads a newer call cancels while they are still in flight, by their paths, such as `["users.search"]` — a search as the user types. The older call rejects with an `HTTPError` of status 499, whatever its variables were. It runs in a browser or a React Native app, where one user is; on a server it has no effect. A path the API does not define, or one that writes, throws `ConfigurationError` `CANCEL_PREVIOUS_INVALID` where the API is defined. See [Deduplication & Abort](/docs/advanced/abort#cancelling-the-previous-call).

### allowedHeaders

Additional header names the proxy carries for every endpoint of the source, in both directions: forwarded from the browser to the backend, and passed back from the backend's answer to the browser. An endpoint names its own with `.headers([...])`, which adds to this list rather than replacing it. The lists govern only what the proxy passes along: headers the source, a middleware or an endpoint's own closure sets are written by you and are always sent, and a browser's copy of one of them is dropped rather than sent in its place.

```ts
const users = defineGroup({
  prefix: "users",
  endpoints: {
    list: endpoint.get("").headers(["x-total-count", "link"]),
    create: endpoint.post("").headers(["location"]),
  },
})
```

Cookies are named the same way, one direction at a time: `cookie` forwards the caller's cookies to the upstream, and `set-cookie` passes the upstream's back. Neither crosses where it is not named — see [Cookies](/docs/proxy-mode/api-route#cookies). An API that keeps a [`session`](#session) names its cookies there instead, and refuses `cookie` and `set-cookie` in these lists with `SESSION_HEADER_CONFLICT`: they would carry every cookie past it.

The headers that describe the body — `Content-Type`, `Content-Length`, `Content-Encoding` — are never taken from these lists, because the proxy sends a body of its own.

### session

How the API keeps the user's session: the cookies it is kept in, by name or by a prefix ending in `*`; a cookie holding a bearer token; the double-submit token a write repeats; whether the session cookies it sets reach the browser; the other origins whose pages may make writes with it. A call made while a server serves a user carries that user's session from the request being served; a call made in a browser lets the browser send its own cookies.

```ts
defineApi({
  baseURL: "https://api.example.com",
  session: {
    cookies: ["sessionid", "logto_*"],
    bearer: "access_token",
    csrf: { cookie: "csrftoken", header: "x-csrftoken" },
  },
  endpoints,
})
```

An endpoint takes it with `session: false` when it answers every user alike, and `session: "unguarded"` for a write that does no harm when another site sets it off, such as signing out. See [User Sessions](/docs/server-side/session).

### forward

The headers a call made while serving a request carries from it: `requestId`, `trace`, `clientAddress`, `userAgent`, `locale`, and further `headers` by name. `requestId` and `clientAddress` also take header names, for an API that logs the request's id or reads the user's address under names of its own. An endpoint keeps them off its calls with `forward: false`. See [User Sessions](/docs/server-side/session#forward).

## Related

- [User Sessions](/docs/server-side/session) — the session and the forwarded headers, end to end
- [defineGroup](/docs/api/define-group) — defining endpoint groups
- [Defining APIs Guide](/docs/core-concepts/defining-apis) — in-depth usage patterns
- [Middleware](/docs/advanced/middleware) — middleware reference
