@skein-js/express
v0.16.0
Published
Express adapter for skein-js — mount the Agent Protocol on an Express Router.
Maintainers
Readme
@skein-js/express
Express adapter for skein-js — mount the Agent Protocol on an Express
Router.
Part of skein-js — the open-source alternative to LangGraph Platform for TypeScript: a self-hosted Agent Protocol server for LangGraph.js, and a drop-in replacement for the LangGraph CLI.
Status: 🚧 Pre-alpha — implemented; the v1 framework adapter.
What it does
Converts Express req/res into @skein-js/agent-protocol's normalized
request, dispatches to its handler table, and pipes JSON / 204 / text/event-stream responses back
out. It adds no protocol logic of its own — it's a thin transport shim. It ships:
createExpressServer— a zero-setup server (reads alanggraph.json, wires in-memory drivers).skeinRouter— a mountableRouterfor an existing Express app.createHandlerRouter— the pure shim for callers wiring their ownProtocolDeps.
Install
pnpm add @skein-js/express @langchain/langgraphexpress (>=4.18) and @langchain/langgraph are peer dependencies. express is almost
always already in your app; add it too if not (pnpm add express).
Usage
The zero-setup server — reads a langgraph.json, wires in-memory drivers, and serves the protocol:
import { createExpressServer } from "@skein-js/express";
const server = await createExpressServer({ config: "./langgraph.json" });
await server.listen(2024); // defaults: port 2024, host "localhost"
// …later:
await server.close();Mount the router on an existing Express app:
import express from "express";
import { skeinRouter } from "@skein-js/express";
const app = express();
const { router, runtime } = await skeinRouter({ config: "./langgraph.json" });
app.use(router);
app.listen(2024);
// runtime.worker.stop() to drain background runs on shutdown.Bring your own persistent drivers (e.g. Postgres + Redis, assembled by
@skein-js/runtime) through the same deps seam:
import { skeinRouter } from "@skein-js/express";
import { buildRuntime } from "@skein-js/runtime";
const runtime = await buildRuntime({
configPath: "./langgraph.json",
store: "postgres",
queue: "redis",
});
const { router } = await skeinRouter({ deps: runtime.deps, cors: runtime.cors });
app.use(router);Graphs as plain endpoints (non-chat)
For workloads that aren't chat — a classifier, an extractor, a workflow another service calls — there
is a smaller surface: every graph mounted as POST /invoke/:graph_id, where the request body is
the graph input and the response is the final state. No threads, assistants, or runs.
import { skeinInvokeRouter } from "@skein-js/express";
import { embedInMemoryGraphs } from "@skein-js/server-kit";
const { router } = await skeinInvokeRouter({ deps: embedInMemoryGraphs({ triage }) });
app.use(router);
// curl -X POST localhost:2024/invoke/triage -d '{"text":"…"}'Send Accept: text/event-stream to stream the steps instead. See
docs/serving-a-single-graph.md and
examples/invoke-endpoint.
API
createExpressServer(options): Promise<SkeinExpressServer>—SkeinExpressServer={ app, runtime, listen(port?, host?), close() }.listendefaults to port2024, host"localhost", resolves the NodeServeronce bound;close()stops the run worker then the HTTP server (idempotent).skeinRouter(options): Promise<SkeinRouter>—SkeinRouter={ router, runtime }.skeinInvokeRouter(options): Promise<SkeinInvokeRouter>— the simplified serving surface (POST /invoke/:graph_id, body-in / final-state-out).SkeinInvokeRouter={ router, deps }; options addprefix(default/invoke),streamMode, andjson.SkeinRouterOptions— common{ logger?, cors?, warm?, json?, requestLog? }plus either{ config, importModule? }(in-memory runtime from alanggraph.json) or{ deps }(bring-your-ownProtocolDeps).warm: trueeagerly loads graphs at startup.json: { limit }— body-parser limit, defaultexpress.json()'s 100kb. A run'sinputis a graph state, so a long message history or a base64 attachment passes 100kb easily.skeinInvokeRoutertakes the same option and needs it more, since that endpoint carries the whole input in one body.requestLog— a line per HTTP request. Defaults to whether you passed alogger.
Logging — unlike the NestJS and Fastify adapters, which default to the host framework's own logger, Express owns none, so skein stays silent here until you pass one — a library should not decide on its host's behalf to start writing to stdout.
loggerreaches the run engine, so failed graph runs and webhook failures are reported, and by default it also mounts per-request logging. Those are separable: a production server wants the reports without two lines per request, which is whatrequestLog: falseis for (and whatskein startdoes).createConsoleLogger({ level?, prefix? })(re-exported from@skein-js/server-kit) is the one-liner. See errors-and-logging.md.createHandlerRouter(handlers, options?)/skeinRoutes— the pure route table, for composing your own routing over an existingProtocolHandlers.corsFromHttpConfig(http)/toCorsOptions(config)— map alanggraph.jsonhttp.corsblock ontocorsoptions (LanggraphCorsConfig).loadInMemoryRuntime/loadReloadableInMemoryRuntime— the in-memoryProtocolDepsloaders (the reloadable one addsreloadGraphs/snapshotState/hydrateState, poweringskein dev).Low-level mappers:
toProtocolRequest,sendProtocolResponse,sendErrorResponse.toProtocolRequestdoes not setsignal. The shipped router spreads in anAbortSignaltied to the response'sclose, which is what makeson_disconnect: "cancel"work — so a hand-rolled route built from this mapper alone silently never cancels on disconnect. Add{ ...toProtocolRequest(req), signal }if you need it.
CORS
Browser clients (Agent Chat UI, React useStream) run on a different origin. CORS is off by
default (non-permissive) and driven by the http.cors block of langgraph.json, matching the
LangGraph CLI:
{
"graphs": { "agent": "./agent.ts:graph" },
"http": { "cors": { "allow_origins": ["http://localhost:3000"] } },
}Override in code with the cors option: pass CorsOptions
to restrict origins, true for permissive dev, or false to force it off.
Reuse
Thin transport shim over @skein-js/agent-protocol; adds no protocol logic of
its own.
Learn more
- Agent Protocol surface · Streaming (SSE) · React SDK /
useStream - skein-js overview · Reuse-first architecture · Root README
