Server
serve, the HTTP front end that puts a server node's routes, auth, and SSE stream in front of an Engine.
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
function serve(options: ServeOptions): Promise<Server>;
| Parameter | Type | Default | Description |
|---|---|---|---|
options |
ServeOptions |
(required) | See ServeOptions. |
Returns the running Server.
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 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 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) 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
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.
Server
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 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
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
const handlers: Handlers<NodeContext>;
The implementation of every procedure in the contract, keyed by procedure name. What serve’s /rpc/:procedure route dispatches into.