# createCaller

Factory function that creates a typed caller for server-side direct HTTP calls — no proxy hop needed.

## Import

```ts
import { createCaller } from "@noverax/courier"
```

## Signature

`createCaller` takes your API definition and optional defaults, returning a `TypedClient`. Direct mode calls the backend directly without going through a proxy.

```ts
import type { CallerDefaults, TypedClient } from "@noverax/courier"
import type { DefinedApi } from "@noverax/courier/define"

declare function createCaller<TApi extends DefinedApi>(
  api: TApi,
  defaults?: CallerDefaults,
): TypedClient<TApi>
```

## Parameters

### api

An API source definition created with `defineApi()`.

```ts
import { api } from "@/api"

const caller = createCaller(api)
```

### CallerDefaults

Optional default settings applied to every request.

```ts
type CallerDefaults = {
  timeout?: number
  headers?: Record<string, string>
  context?: Context
  onError?: (error: HTTPError) => void | Promise<void>
  onSuccess?: (data: unknown) => void | Promise<void>
  ws?: CallerWSOptions
}

type CallerWSOptions = {
  auth?: WSAuthParams | (() => WSAuthParams | undefined | Promise<WSAuthParams | undefined>)
  backoff?: { initialMs?: number; maxMs?: number; factor?: number; jitter?: boolean }
  heartbeat?: { intervalMs?: number; timeoutMs?: number }
  messageQueue?: { maxCount?: number; maxBytes?: number }
  protocols?: string[]
  maxMessageSize?: number
}

type WSAuthParams = Record<string, unknown>
```

- `ws` — options of the sockets this caller's channels and push subscriptions open; see [Connection options](/docs/advanced/realtime#connection-options).

## Return Type

A call resolves to the response data. Through `.raw` it resolves to the whole response, `HttpResponse<R>`, with `status`, `headers` and `data`; with `.with({ stream: true })` the data is the unread `ReadableStream`.

```ts
type HttpResponse<T> = {
  status: number
  headers: Headers
  data: T
}
```

## Basic Usage

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

export const caller = createCaller(api, {
  timeout: 10000,
})

// Usage: caller.group.method(variables?)
const result = await caller.users.getAll()
const user = await caller.users.getById({ id: "123" })
```

## Streams and channels

A direct caller opens a stream or a channel at the API's `baseURL`, as it sends any other call there. One served by this process with `.implement()` is not at `baseURL` — that is the upstream's address — so calling it through `createCaller` throws `ConfigurationError("DIRECT_CALL_TO_SERVED_ENDPOINT")`, naming the endpoint, when it is called; the rest of the API stays callable. Reach it through [`createProxyCaller`](/docs/api/create-proxy-caller) and the server that serves it — see [Streams & Channels](/docs/advanced/realtime#reaching-a-stream-or-a-channel).

## Passing Context

`defaults.context` is a `Context` object. Use it to forward headers from the incoming request to the backend: `authorization`, `user-agent`, `referer`, `origin` and `x-request-id`, the headers the source and the endpoint name, and the user's cookies where they name `cookie`. A context belongs to one incoming request, so build the caller for that request:

```ts
type Context = {
  req?: Request
  res?: Response
  headers?: Headers
}
```

```ts title="app/api/profile/route.ts"
import { createCaller } from "@noverax/courier"
import { api } from "@/api"

export async function GET(req: Request) {
  // The session the API describes is read from this request
  const caller = createCaller(api, { context: { req } })
  const profile = await caller.users.getProfile()

  // Headers of your own, for one call
  const user = await caller.users
    .with({ headers: { "x-tenant": "acme" } })
    .getById({ id: "123" })

  return Response.json({ profile, user })
}
```

An API that keeps a `session` takes the user's session from the request a context names, and a framework adapter finds that request without one — see [User Sessions](/docs/server-side/session). Handing a caller `await headers()` whole sends the API every header and every cookie the browser addressed to your application.

## Server Components

Direct mode is designed for server-only execution. Use it in React Server Components, API routes, server actions, or any backend runtime.

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

export default async function UsersPage() {
  const result = await caller.users.getAll()

  return (
    <ul>
      {result.data?.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}
```

## Server Actions

```ts title="app/users/actions.ts"
"use server"

import { caller } from "@/lib/caller"
import { revalidatePath } from "next/cache"

export async function createUser(formData: FormData) {
  const name = formData.get("name") as string
  await caller.users.create({ name })
  revalidatePath("/users")
}
```

## Configuring Defaults

```ts title="lib/caller.ts"
const caller = createCaller(api, {
  timeout: 15000,
  headers: {
    "X-Internal-Service": "courier-app",
  },
  onError: (error) => {
    console.error(`[Direct] HTTP ${error.status}:`, error.data)
  },
})
```

## Related

- [Server-Side Usage](/docs/server-side/overview) — direct mode architecture
- [Server Actions](/docs/server-side/actions) — mutations with revalidation
- [createProxyCaller](/docs/api/create-proxy-caller) — proxy-mode alternative for client components
- [defineApi](/docs/api/define) — defining data sources
