---
title: Server
description: serve, the HTTP front end that puts a server node's routes, auth, and SSE stream in front of an Engine.
---

```ts
import { serve } from "lakefront/server";
```

Opens an `Engine` and puts an HTTP front end on it: routes, verb-gated auth, and an SSE change stream.

## Functions

### serve

```ts
function serve(options: ServeOptions): Promise<Server>;
```

| Parameter | Type           | Default    | Description                        |
| --------- | -------------- | ---------- | ---------------------------------- |
| `options` | `ServeOptions` | (required) | See [ServeOptions](#serveoptions). |

**Returns** the running [Server](#server).

```ts
const node = await serve({
  postgres: "postgres://lakefront:lakefront@127.0.0.1:5432/lakefront",
  data: "./data",
  port: 8080,
});
```

Opens an `Engine` (via `open`, from `lakefront`) and puts an HTTP front end on it. `ServeOptions` extends `OpenOptions`; see the [engine reference](/reference/engine) for every inherited knob, including the two required fields (`postgres`, `data`) and everything `open()` defaults for you.

## Endpoints

| Route             | Method | What it does                                                                                      |
| ----------------- | ------ | ------------------------------------------------------------------------------------------------- |
| `/health`         | `GET`  | Liveness. Does no DuckDB work. A cold node still answers.                                         |
| `/rpc/:procedure` | `POST` | Every procedure in the wire contract (`query.execute`, `mutate.*`, `changes.read`, `node.stats`). |
| `/watch`          | `GET`  | Server-sent-events change stream.                                                                 |
| anything else     | any    | `404`, `{ "error": "not found" }`.                                                                |

See the [wire protocol reference](/reference/wire-protocol) for request and response formats, headers, status codes, and SSE events.

## Verb gate

Every procedure requires a verb, checked against the token's claims (with auth on) before its handler runs:

| Procedure       | Verb    |
| --------------- | ------- |
| `query.execute` | `read`  |
| `changes.read`  | `read`  |
| `node.stats`    | `read`  |
| `mutate.insert` | `write` |
| `mutate.update` | `write` |
| `mutate.delete` | `write` |

`/watch` is gated by the same `read` verb outside this table, since it is not a `/rpc/:procedure` call.

`schema` and `admin` are valid verbs on a token (see the [wire contract](/reference/contract)) but no wire procedure currently requires either: schema and lifecycle operations (`createBoard`, `dropBoard`, `addColumn`, and so on) are engine-level admin decisions, not exposed over HTTP today, so those two verbs gate nothing on the wire.

## Types

### ServeOptions

`OpenOptions` plus three fields:

| Field    | Type         | Default                     | Notes                                                                                                                                  |
| -------- | ------------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `port`   | `number`     | `0`                         | `0` asks the OS to pick a free port; read back the bound port from the returned node.                                                  |
| `nodeId` | `string`     | `` `node-${process.pid}` `` | Identifies this node in `node.stats()` and a query's `servedBy` field. Set it explicitly in anything with more than one process.       |
| `auth`   | `AuthConfig` | unset                       | Capability-token auth. Unset means auth is off: every request is permitted, the zero-config local default the tour and demo run under. |

### AuthConfig

```ts
type AuthConfig = { secret: string | Uint8Array } | { registry: true };
```

| Variant              | Behavior                                                       |
| -------------------- | -------------------------------------------------------------- |
| `{ secret }`         | Verifies every token with one static HMAC key (`kid` `"env"`). |
| `{ registry: true }` | Loads rotating keys from the catalog Postgres, keyed by `kid`. |

Minting, attenuating, and rotating tokens are covered in the [auth guide](/guides/auth).

### Server

```ts
interface Server {
  readonly nodeId: string;
  readonly port: number;
  readonly url: string;
  readonly engine: Engine;
  stop(): Promise<void>;
}
```

`engine` is the underlying `Engine` instance directly: `flush`, `maintain`, `stats`, `evict`, `watch`, and everything else in the [operations guide](/guides/operations) is reachable without a second connection to the lake. `stop()` closes the HTTP server, waits for in-flight replica work to settle, closes the auth key registry if one was opened, and closes the engine.

### NodeContext

```ts
interface NodeContext {
  readonly boardId: string;
  readonly engine: Engine;
  readonly nodeId: string;
  readonly actor?: string; // verified audit identity, when auth is on
}
```

Import `handlers` and `NodeContext` from `lakefront/server` to use the procedure implementations in your own HTTP server.

`boardId` and `actor` are expected to come from your own request handling (board resolved from a verified claim or header, never from the SQL): `handlers` itself does no auth and no board resolution.

## Constants

### handlers

```ts
const handlers: Handlers<NodeContext>;
```

The implementation of every procedure in the contract, keyed by procedure name. What `serve`'s `/rpc/:procedure` route dispatches into.
