Skip to content

API ReferencecreateCaller

createCaller

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

2 min readdirect mode

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.

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

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 and the server that serves it — see Streams & Channels.

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

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

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

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)
  },
})