# Serving & Clients

With the API defined and the handlers implemented, two things are derived from
the same `Api` value: a **server** that mounts the routes and a **typed client**
whose methods mirror the endpoints. This page wires both, then enumerates the
full surface of `HttpApiBuilder`, `HttpApiClient`, and `HttpApiTest`.

## Assembling the server

`HttpApiBuilder.layer(Api)` turns the API plus its handler groups into routes.
Provide each group's handler `Layer`, then serve the result with a platform HTTP
server. The example also mounts interactive [docs](https://effect.plants.sh/http-api/openapi/) and
exposes the OpenAPI JSON.

```ts
// server/main.ts
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { Layer } from "effect"
import { HttpRouter } from "effect/unstable/http"
import { HttpApiBuilder, HttpApiScalar } from "effect/unstable/httpapi"
import { createServer } from "node:http"
import { Api } from "../api/Api.ts"
import { AuthorizationLayer } from "./Authorization.ts"
import { SystemApiHandlers } from "./System/http.ts"
import { Users } from "./Users.ts"
import { UsersApiHandlers } from "./Users/http.ts"

// Build the API routes and serve the OpenAPI document at /openapi.json
const ApiRoutes = HttpApiBuilder.layer(Api, {
  openapiPath: "/openapi.json"
}).pipe(
  // Provide every group's handler Layer (and their dependencies)
  Layer.provide([
    UsersApiHandlers.pipe(Layer.provide([Users.layer, AuthorizationLayer])),
    SystemApiHandlers
  ])
)

// Serve interactive Scalar docs at /docs
const DocsRoute = HttpApiScalar.layer(Api, { path: "/docs" })

// Combine all the route layers
const AllRoutes = Layer.mergeAll(ApiRoutes, DocsRoute)

// Mount the routes on a Node HTTP server listening on port 3000
const HttpServerLayer = HttpRouter.serve(AllRoutes).pipe(
  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)

// Launch and run. Layer.launch keeps the server alive until interrupted.
Layer.launch(HttpServerLayer).pipe(NodeRuntime.runMain)
```
**Tip:** The platform layer is the only host-specific part. Swap `NodeHttpServer` for
`BunHttpServer` to run on Bun, or use a web handler (below) for serverless. The
API definition, handlers, and routes are identical across hosts — see
[Platform](https://effect.plants.sh/platform/).

### `HttpApiBuilder.layer` options

`layer` registers every group's routes onto the ambient `HttpRouter`. It returns
a `Layer` that fails loudly (via `Effect.die`) if a group's handler layer was not
provided, naming the missing `HttpApiBuilder.group(api, "<id>", ...)`.

The only option is **`openapiPath`** — a `` `/${string}` `` path on which to
serve the generated OpenAPI 3.1 JSON document. Omit it to skip the OpenAPI route.

```ts
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

// With the OpenAPI document mounted at GET /openapi.json
HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" })

// Without — just the API routes, no spec endpoint
HttpApiBuilder.layer(Api)
```

The layer requires the platform services a router needs to encode responses:
`Etag.Generator`, `FileSystem`, `HttpPlatform`, `Path`, the `HttpRouter`, and the
`ToService` of every group (i.e. the handler layers).

### Serverless / Web standard handler

For environments that speak the Fetch API (edge functions, workers), produce a
`(request: Request) => Promise<Response>` handler instead of binding a port.
`HttpRouter.toWebHandler` builds the handler from a route `Layer`;
`HttpServer.layerServices` supplies the platform services a router needs **without**
a running server:

```ts
import { Layer } from "effect"
import { HttpRouter, HttpServer } from "effect/unstable/http"

// `handler` is (request: Request) => Promise<Response>; `dispose` tears down
// the scoped runtime the handler was built with.
export const { handler, dispose } = HttpRouter.toWebHandler(
  AllRoutes.pipe(Layer.provide(HttpServer.layerServices))
)
```
**Note:** `HttpRouter.serve` and `HttpRouter.toWebHandler` come from the underlying HTTP
layer, not from `httpapi`. They work with any router/route layer, not just ones
built from an `HttpApi`. See the [HTTP client](https://effect.plants.sh/http-client/) section for the
`HttpClient` side of that layer.

## The typed client

`HttpApiClient.make(Api)` builds a client whose shape exactly follows the API:
non-`topLevel` groups become nested namespaces, endpoints become methods, and
`topLevel` groups attach their methods directly to the root. Inputs and outputs
are typed and validated against the same schemas the server uses.

```ts
// client/ApiClient.ts
import { Context, flow, Layer, Schedule } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
import { HttpApiClient } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"
import { AuthorizationClient } from "./AuthorizationClient.ts"

export class ApiClient extends Context.Service<
  ApiClient,
  // ForApi derives the full client interface from the API definition
  HttpApiClient.ForApi<typeof Api>
>()("acme/ApiClient") {
  static readonly layer = Layer.effect(
    ApiClient,
    HttpApiClient.make(Api, {
      // transformClient adjusts the underlying HttpClient: set the base URL,
      // add retries, attach default headers, etc.
      transformClient: (client) =>
        client.pipe(
          HttpClient.mapRequest(flow(
            HttpClientRequest.prependUrl("http://localhost:3000")
          )),
          HttpClient.retryTransient({
            schedule: Schedule.exponential(100),
            times: 3
          })
        )
    })
  ).pipe(
    // Provide the client implementation of any required client middleware
    Layer.provide(AuthorizationClient),
    // Provide an HttpClient backend. FetchHttpClient works in the browser and
    // edge runtimes; NodeHttpClient / BunHttpClient are also available.
    Layer.provide(FetchHttpClient.layer)
  )
}
```

Calling the API is now just calling methods. The compiler enforces argument
shapes and infers the result and error types from the endpoint definitions:

```ts
import { Effect } from "effect"
import { UserId } from "../domain/User.ts"
import { ApiClient } from "./ApiClient.ts"

export const program = Effect.gen(function*() {
  const client = yield* ApiClient

  // topLevel "system" group -> method on the root
  yield* client.health()

  // "users" group -> client.users.<endpoint>. Each method takes one object
  // whose keys (params / query / payload / headers) mirror the endpoint inputs.
  // `id` decodes to the branded UserId, so build it with UserId.make.
  const user = yield* client.users.getById({ params: { id: UserId.make(1) } })
  const created = yield* client.users.create({
    payload: { name: "Ada", email: "ada@acme.dev" }
  })
  const all = yield* client.users.list({ query: { search: "ada" } })

  return { user, created, all }
}).pipe(Effect.provide(ApiClient.layer))
```
**Note:** The client is generated from the definition, so renames and schema changes are
checked end-to-end at compile time. Remove an endpoint and every call site stops
compiling; change a payload field and the `create` call above must be updated.

### How requests are encoded

Each generated method takes a single request object whose keys mirror the
endpoint's `params`, `query`, `payload`, and `headers` schemas. An endpoint with
no inputs accepts `void` (`client.health()`), so the only optional key is always
`responseMode` (below). Encoding follows fixed rules:

- **Path params** are encoded with the endpoint's `params` schema and substituted
  into the path template (`/users/:id`).
- **Payloads** on body methods (`POST`, `PUT`, `PATCH`) are encoded to a request
  body. On bodyless methods (`GET`, `DELETE`) the payload is encoded into **URL
  parameters** instead.
- **Multipart** payloads must be supplied as a `FormData` instance — the type of
  the `payload` key narrows to `FormData` for multipart endpoints.
- **Query** values and **headers** are encoded with their respective schemas and
  appended/set on the request.

### Decoding & error channel

A successful response is decoded with the matching success schema for its status
code; a declared error response decodes into the endpoint's error type. The
effect's error channel is the union of: the endpoint's declared `_Error` type,
any middleware errors, `HttpClientError.HttpClientError`, and `Schema.SchemaError`
when decoding fails. An unmatched status fails as an `HttpClientError` carrying a
`DecodeError`.

## Response mode

Every generated method accepts an optional `responseMode` on its request object.
The mode is the `ClientResponseMode` type from `HttpApiEndpoint`:

```ts
type ClientResponseMode = "decoded-only" | "decoded-and-response" | "response-only"
```

It controls what the call returns and which parts of the pipeline run:

- **`"decoded-only"`** (the default) — returns the decoded success value.
- **`"decoded-and-response"`** — returns a tuple `[value, response]`, pairing the
  decoded value with the raw `HttpClientResponse` (handy for reading status codes
  or response headers alongside the body).
- **`"response-only"`** — skips success **and** error decoding and returns the raw
  `HttpClientResponse`. Because nothing is decoded, the endpoint error type and
  `SchemaError` drop out of the error channel — use this for custom response
  handling.

```ts
import { Effect } from "effect"
import { UserId } from "../domain/User.ts"
import { ApiClient } from "./ApiClient.ts"

export const modes = Effect.gen(function*() {
  const client = yield* ApiClient
  const params = { id: UserId.make(1) }

  // Default: the decoded User
  const user = yield* client.users.getById({ params })
  // => User

  // Decoded value paired with the raw response — read headers / status alongside it
  const [user2, response] = yield* client.users.getById({
    params,
    responseMode: "decoded-and-response"
  })
  // => [User, HttpClientResponse]
  response.status // => 200

  // Raw response only — no decoding, narrower error channel
  const raw = yield* client.users.getById({ params, responseMode: "response-only" })
  // => HttpClientResponse
  const body = yield* raw.text
}).pipe(Effect.provide(ApiClient.layer))
```

## Testing without a network

`HttpApiTest` builds an **in-memory** client that drives your real handlers
through the same encode/decode/routing pipeline as a live server — no port, no
sockets. Provide the handler layers for the groups under test, then call the
client just like the real one.

```ts
// test/Users.test.ts
import { assert, describe, it } from "@effect/vitest"
import { Effect } from "effect"
import { HttpApiTest } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"
import { UsersApiHandlers } from "../server/Users/http.ts"

describe("Users API", () => {
  it.effect("create + getById round-trip", () =>
    Effect.gen(function*() {
      // Build the in-memory client for the "users" group. Unselected groups
      // are still present on the client but die if called.
      const client = yield* HttpApiTest.groups(Api, ["users"])

      const created = yield* client.users.create({
        payload: { name: "Ada", email: "ada@acme.dev" }
      })
      assert.strictEqual(created.name, "Ada")

      const fetched = yield* client.users.getById({ params: { id: created.id } })
      assert.deepStrictEqual(fetched, created)
    }).pipe(
      // Supply the real handler layer for the selected group
      Effect.provide(UsersApiHandlers)
    ))
})
```

`HttpApiTest.groups` is itself an `Effect`, so it must be `yield*`-ed inside a
test effect; the selected groups are taken from the environment (provide their
`HttpApiBuilder.group` layers). See [Testing](https://effect.plants.sh/testing/) for the broader testing
story.

---

## `HttpApiBuilder` reference

Server-side assembly: turning groups and endpoints into routes.

### `HttpApiBuilder.layer`

Registers an entire `HttpApi` onto the ambient `HttpRouter`, requiring every
group's handler layer. Accepts `{ openapiPath }` to additionally serve the
OpenAPI document. (See [options](#httpapibuilderlayer-options) above.)

```ts
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

const ApiRoutes = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" })
// => Layer<never, never, HttpRouter | ToService<...> | platform services>
```

### `HttpApiBuilder.group`

Implements all endpoints in one group. The `build` callback receives a mutable
`Handlers` instance; call `handlers.handle(name, handler)` (or `handleRaw`) for
each endpoint. The result `Layer` provides that group's `ApiGroup` service.

```ts
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

export const SystemApiHandlers = HttpApiBuilder.group(
  Api,
  "system",
  Effect.fn(function*(handlers) {
    return handlers.handle("health", () => Effect.void)
  })
)
// => Layer<ApiGroup<"Api", "system">, ...>
```
**Note:** The `build` callback may be a plain function that returns the `Handlers`, or an
`Effect` (use `Effect.gen` / `Effect.fn`) when constructing handlers needs the
environment. See [Handlers](https://effect.plants.sh/http-api/handlers/) for `handle` vs `handleRaw`.

### `HttpApiBuilder.Handlers`

The mutable handler collection passed to `group`'s callback. Its methods refine
the type-level set of not-yet-implemented endpoints:

- **`handle(name, handler, options?)`** — implement an endpoint with automatic
  payload decoding and success encoding.
- **`handleRaw(name, handler, options?)`** — opt out of automatic payload
  decoding and receive the raw request (e.g. for streaming bodies).

Both accept `{ uninterruptible?: boolean }`. The `Handlers.Item`, `Handlers.Error`,
and `Handlers.Context` helper types describe the registered handler and the error
and service channels it contributes.

```ts
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

export const UsersApiHandlers = HttpApiBuilder.group(
  Api,
  "users",
  Effect.fn(function*(handlers) {
    return handlers
      .handle("getById", (req) => getUser(req.params.id))
      .handle("create", (req) => createUser(req.payload))
  })
)

declare function getUser(id: unknown): Effect.Effect<any>
declare function createUser(payload: unknown): Effect.Effect<any>
```

### `HttpApiBuilder.endpoint`

Builds the server-side HTTP `Effect` for a **single** endpoint, given its group
name, endpoint name, and a handler. Useful when mounting one endpoint manually
rather than a whole group via `layer`.

```ts
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

const healthRoute = HttpApiBuilder.endpoint(Api, "system", "health", () => Effect.void)
// => Effect<Effect<HttpServerResponse, never, ...>, never, ...>
```

### `HttpApiBuilder.handlerToRoute`

Lower-level helper that converts a group + a `Handlers.Item` + a `Context` into an
`HttpRouter.Route`. This is the primitive `layer` and `HttpApiTest` use internally;
reach for it only when building custom routing on top of `HttpApi`.

```ts
import { HttpApiBuilder } from "effect/unstable/httpapi"

// HttpApiBuilder.handlerToRoute(group, handlerItem, context)
// => HttpRouter.Route — the method + path + compiled handler effect
```

---

## `HttpApiClient` reference

Client-side derivation: turning groups and endpoints into callable methods.

### `HttpApiClient.make`

The common entry point. Takes the `HttpClient` from the Effect environment and
returns the full client. Options: `transformClient` (adjust the underlying
`HttpClient`), `transformResponse` (wrap the per-call decode effect), and
`baseUrl` (prepended to every request URL).

```ts
import { Effect } from "effect"
import { HttpApiClient } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

const make = HttpApiClient.make(Api, { baseUrl: "http://localhost:3000" })
// => Effect<Client<...>, never, HttpClient | MiddlewareClient<...>>
```
**Note:** `baseUrl` and `transformClient` both let you set the host. `baseUrl` simply
prepends the URL; `transformClient` is the general escape hatch for retries,
default headers, logging, etc. (as in the [client setup](#the-typed-client) above).

### `HttpApiClient.makeWith`

Like `make`, but takes an explicit `HttpClient` (`{ httpClient }`) instead of
reading it from the environment — for when you already hold a concrete or
transformed client. Supports `transformResponse` and `baseUrl`. This is what
`HttpApiTest.groups` uses with its in-memory client.

```ts
import { Effect } from "effect"
import { HttpClient } from "effect/unstable/http"
import { HttpApiClient } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

const makeWith = HttpClient.HttpClient.pipe(
  Effect.flatMap((httpClient) =>
    HttpApiClient.makeWith(Api, { httpClient, baseUrl: "http://localhost:3000" })
  )
)
```

### `HttpApiClient.group`

Builds the typed client object for **one** group only, filtering the API down to
that group. Returns a `Client.Group` (the nested object of endpoint methods).

```ts
import { Effect } from "effect"
import { HttpClient } from "effect/unstable/http"
import { HttpApiClient } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

const usersClient = HttpClient.HttpClient.pipe(
  Effect.flatMap((httpClient) =>
    HttpApiClient.group(Api, { group: "users", httpClient })
  )
)
// usersClient.getById({ params: { id } }), usersClient.create({ payload })
```

### `HttpApiClient.endpoint`

Builds the typed method for a **single** endpoint in a group. Returns just the
callable function. Supports `transformClient`, `transformResponse`, and `baseUrl`.

```ts
import { Effect } from "effect"
import { HttpClient } from "effect/unstable/http"
import { HttpApiClient } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

const getById = HttpClient.HttpClient.pipe(
  Effect.flatMap((httpClient) =>
    HttpApiClient.endpoint(Api, { group: "users", endpoint: "getById", httpClient })
  )
)
// yield* getById({ params: { id } }) => User
```

### `HttpApiClient.urlBuilder`

Mirrors the client layout but returns **URL strings** instead of executing
requests — it encodes only `params` and `query`. Useful for links, redirects, or
constructing requests by hand. Accepts an optional `baseUrl`.

```ts
import { Schema } from "effect"
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"

const Api = HttpApi.make("Api").add(
  HttpApiGroup.make("users").add(
    HttpApiEndpoint.get("getUser", "/users/:id", {
      params: { id: Schema.String }
    })
  )
)

const buildUrl = HttpApiClient.urlBuilder(Api, { baseUrl: "https://api.example.com" })

buildUrl.users.getUser({ params: { id: "123" } })
// => "https://api.example.com/users/123"
```

### `HttpApiClient.makeClient`

Internal driver shared by `make`, `makeWith`, `group`, and `endpoint`. It reflects
over the API, building per-endpoint encode/decode functions and invoking
`onGroup` / `onEndpoint` callbacks. You will not normally call it directly — use
one of the constructors above.

### Client types: `Client`, `ForApi`, `Client.Group`, `Client.Method`, `UrlBuilder`

- **`Client<Groups, E, R>`** — the generated client object: nested objects for
  non-`topLevel` groups, methods on the root for `topLevel` group endpoints.
- **`ForApi<Api, E, R>`** — derives `Client` straight from an `HttpApi` type. Use
  it as the service shape: `Context.Service<ApiClient, HttpApiClient.ForApi<typeof Api>>`.
- **`Client.Group<Groups, Name, E, R>`** — the object for one group (returned by
  `HttpApiClient.group`).
- **`Client.Method<Endpoint, E, R>`** — the function generated for one endpoint;
  generic over the response `Mode`.
- **`Client.ResponseMode`** / **`Client.Response<Success, Mode>`** — the per-call
  `responseMode` type and the value it produces.
- **`UrlBuilder<Api>`** — the shape returned by `urlBuilder`.

```ts
import { Context } from "effect"
import { HttpApiClient } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"

export class ApiClient extends Context.Service<
  ApiClient,
  HttpApiClient.ForApi<typeof Api>
>()("acme/ApiClient") {}
```

---

## `HttpApiTest` reference

### `HttpApiTest.groups`

Creates an in-memory client for the named groups of an `HttpApi`. Handlers for the
selected groups are taken from the environment (provide their
`HttpApiBuilder.group` layers); endpoints in **unselected** groups are wired with
placeholder handlers that `die` if called, catching accidental out-of-scope calls.

The returned client is a real `HttpApiClient` — middleware, platform services, and
the optional `baseUrl` behave exactly as in production; only the HTTP transport is
replaced with an in-memory router dispatch.

```ts
import { Effect } from "effect"
import { HttpApiTest } from "effect/unstable/httpapi"
import { Api } from "../api/Api.ts"
import { UsersApiHandlers } from "../server/Users/http.ts"

const test = Effect.gen(function*() {
  // baseUrl is optional; defaults to "http://localhost:3000"
  const client = yield* HttpApiTest.groups(Api, ["users"], {
    baseUrl: "http://localhost:3000"
  })

  const created = yield* client.users.create({
    payload: { name: "Ada", email: "ada@acme.dev" }
  })
  // => User (driven through the real handler, in memory)
}).pipe(Effect.provide(UsersApiHandlers))
```

---

The next step is publishing the contract as [OpenAPI](https://effect.plants.sh/http-api/openapi/).