# createCaller

Factory function for server-side calls — call your backend directly from any server runtime.

## API signature

`createCaller` accepts your API definition and optional defaults. It returns a typed client with dot-notation access to all endpoint groups:

```ts title="@noverax/courier"
declare function createCaller<T extends DefinedApi>(
  api: T,
  defaults?: CallerDefaults
): TypedClient<T>
```

## CallerDefaults

The optional second argument configures defaults applied to every request made through this caller:

```ts
type CallerDefaults = {
  // Request timeout in milliseconds
  timeout?: number
  // Headers sent with every request
  headers?: Record<string, string>
  // Request context passed to every endpoint: headers to forward, the request
  context?: RequestContext
  // Called when a request fails with an HTTPError — an answer with an
  // error status, or a network failure. A ValidationError or a
  // cancellation the caller asked for is thrown without it.
  onError?: (error: HTTPError) => void | Promise<void>
  // Called when any request succeeds
  onSuccess?: (data: unknown) => void | Promise<void>
  // The sockets of this caller's channels and push subscriptions: token,
  // reconnect backoff, heartbeat, queue limits — see Streams & Channels
  ws?: CallerWSOptions
}
```

These defaults are merged with per-request options. Per-request headers are merged (not replaced). The default `timeout` is the least specific: a timeout given for the call wins, then the endpoint's own `.timeout()`, then the default.

```ts title="lib/caller.ts"
import { createCaller } from "@noverax/courier"
import { api } from "@/api"

export const caller = createCaller(api, {
  timeout: 10000,
  headers: {
    "X-Service": "my-app",
  },
  onError: (error) => {
    console.error(`[API] ${error.status}: ${error.message}`)
  },
})
```

> **Global error logging**
>
> The `onError` callback is useful for centralized error logging. It fires before the error propagates to your component, so you can log to Sentry or a custom service without repeating try/catch blocks.

## Basic usage

The returned client uses dot-notation — access a group, then call a method:

```ts title="app/users/page.tsx"
import { caller } from "@/lib/caller"

export default async function UsersPage() {
  // Dot-notation: group.method(variables?)
  const users = await caller.users.getAll({ page: 1, limit: 20 })
  //    ^? UserList — fully typed from your Endpoint definition

  const user = await caller.users.getById({ id: 5 })
  //    ^? User

  return (
    <div>
      <h1>Users ({users.total})</h1>
      <ul>
        {users.data.map((u) => (
          <li key={u.id}>{u.name}</li>
        ))}
      </ul>
    </div>
  )
}
```

## Call signatures

The method accepts different argument combinations depending on whether your endpoint requires variables and whether you need to pass context:

```ts
// No variables, no context
await caller.users.getAll()

// With variables, no context
await caller.users.getById({ id: 5 })

// No variables, with per-call options
await caller.users.with({ timeout: 2_000 }).me()

// With variables and per-call options
await caller.users.with({ headers: { "x-tenant": "acme" } }).create({ name: "Jane" })
```

## The user's session

An API that keeps a session says so where it is defined, with `session`, and a call made while the server serves a user carries that user's session — the cookies the API keeps it in and nothing else — from the request being served. In a browser the platform sends its own cookies. See [User Sessions](/docs/server-side/session) for the options, the setup per framework, and how writes another site sets off are refused.

```ts title="lib/api.server.ts"
import { createServerCaller } from "@noverax/courier-next"
import { api } from "@/api"

// On Next.js: a caller whose calls carry the session of the request being served
export const server = createServerCaller(api)
```

## Context parameter

A caller can also be handed the request it serves, through the `context` default of `createCaller`: its session is then read from that request, and its headers pass the whitelist below. A method takes its variables as its only argument; a second argument is not read.

```ts
// Exported from "@noverax/courier"
type RequestContext = {
  req?: Request      // Web Standard request object
  res?: Response     // Web Standard response object
  headers?: Headers  // Headers to forward (cookies, auth)
}
```

### In React Server Components

A Server Component calls through the caller `createServerCaller` made: the session of the request Next.js is serving travels with each call, and the page is rendered per request.

```ts title="app/profile/page.tsx"
import { server } from "@/lib/api.server"

export default async function ProfilePage() {
  // The user's session cookies — and nothing else of theirs — reach the API
  const profile = await server.users.me()

  return <h1>Welcome, {profile.name}</h1>
}
```

Handing a call `await headers()` whole — `.with({ headers: await headers() })` — sends the API every header the browser addressed to your application, every cookie of it included: its own session, analytics, preferences. Describe the session in the API instead.

### In API routes

In route handlers, give a caller the `Request` as its context. Courier extracts the headers from it, through the whitelist below:

```ts title="app/api/dashboard/route.ts"
import { NextResponse } from "next/server"
import { createCaller } from "@noverax/courier"
import { api } from "@/api"

export async function GET(req: Request) {
  // A caller for this request — its headers are extracted automatically
  const caller = createCaller(api, { context: { req } })
  const stats = await caller.dashboard.getStats()

  return NextResponse.json(stats)
}
```

> **Header whitelist**
>
> Headers carried by a context — `context.req` or `context.headers` — are forwarded only when they are in a safe subset: `authorization`, `user-agent`, `referer`, `origin` and `x-request-id`, plus the names the source lists in `allowedHeaders` or the endpoint in `.headers()`. The user's `cookie` is forwarded from a context only where it is named that way, and so are the credentials a browser sends by itself — an `Authorization` in `Basic`, `Digest`, `Negotiate` or `NTLM`, the answer to your site's own login prompt — while a `Bearer` token goes on. A client address the incoming request claims — `x-forwarded-for`, `x-real-ip` — is not copied; `X-Forwarded-For` is written from the connection and the chain the proxies in front recorded — see [The user's address](/docs/deployment/overview#the-users-address). A header the API sets in its `headers` or its session is sent in place of the request's copy. Headers you pass to `.with({ headers })` are sent as given, except the ones that describe the incoming connection rather than the request — `host`, `connection`, `keep-alive`, `content-length`, `transfer-encoding`, `te`, `trailer`, `upgrade`, `expect` and any header `connection` names — which `fetch` sets for its own.
>
> A header you set yourself — in the caller's `headers` default or with `.with({ headers })` — wins over the forwarded copy of the same name, so a service token is not replaced by the end user's. For the user's session, describe it in the API — see [User Sessions](/docs/server-side/session) — rather than forwarding `cookie` or `await headers()` whole.

## Raw data mode

By default, the request returns the parsed JSON body. If you need access to the HTTP status code and response headers, use `.raw`:

```ts
// Access status code and response headers
const result = await caller.users.raw.getById({ id: 5 })

console.log(result.status)   // 200
console.log(result.headers)  // Headers object
console.log(result.data)     // User — still fully typed
```

## Difference from createProxyCaller

The difference is where the call is made. A proxy caller sends the call to your proxy route, which makes it with the browser's request at hand. `createCaller` makes the call itself, with the request the server is serving at hand, or with the browser's own cookies in a browser:

```ts
// Proxy mode (client components): the proxy route makes the call
const browserCaller = createProxyCaller(apiRoutes)
await browserCaller.users.getAll({ page: 1 })

// Direct mode (server components): the server makes it, with the user's session
const serverCaller = createServerCaller(api)
await serverCaller.users.getAll({ page: 1 })
```

Both use the same `executeRequest` core under the hood, and the same `session` the API describes.

> **Server-only import**
>
> In a Next.js app, never import your server caller in `"use client"` files: the API definition contains your `process.env.API_URL`, which would be exposed in the client bundle. A single-page app that calls its API from the browser uses `createCaller` there on purpose, with an address the browser may know — see [User Sessions](/docs/server-side/session).

## Next Steps

- [Server Actions](/docs/server-side/actions) — type-safe mutations with `createAction`
- [User Sessions](/docs/server-side/session) — the user's session, carried to the API from wherever a call is made
- [createProxyCaller](/docs/proxy-mode/proxy-caller) — the proxy mode equivalent for client components
- [React Query useQuery](/docs/react-query/use-query) — use with proxy mode for client-side data fetching
