Server
The @opentf/web/server entry powers middleware and file-based API routes. Most apps never call these directly — the toolchain wires them up — but they're the contract for TypeScript authoring and for deploying dist/server/api.js to a custom runtime.
Types
import type { ApiHandler, Middleware, MiddlewareContext, MiddlewareRunner, ApiContext, RouteParams, RequestHandler, } from "@opentf/web/server";
| Type | Description |
|---|---|
ApiHandler | (request, context) => Response — a route.* method export (GET, POST, …). |
Middleware | (request, context, next) => Response — a _middleware default export. |
MiddlewareContext | Pre-routing context: url, locals, env?, ctx? — no params/query yet. |
MiddlewareRunner | The createMiddleware runner — run(request, terminal, extras?). |
ApiContext | { params, query, url, locals, env?, ctx? } — handler context (post-routing). |
RequestHandler | (request, env?, ctx?, init?) => Response | null — API dispatch; null = no match. |
ApiContext / locals
| Field | Type | Description |
|---|---|---|
params | RouteParams | Dynamic segments ([param] → string, [...rest] → string[]). |
query | Record<string, string> | Parsed query string. |
url | URL | Parsed request URL. |
locals | Record<string, unknown> | Mutable bag; pipeline middleware writes, handlers and loaders read. |
env | unknown | Workers bindings (env.DB, KV, secrets); undefined on Bun/Node. |
ctx | { waitUntil } | Workers execution context. |
Functions
createMiddleware(middlewareModules?, options?)
Builds a pipeline middleware runner from discovered _middleware modules (keyed by absolute file path). Scope matching governs pages, API routes, __data.json, and SSR. Options: appDir, i18n (locale-prefix stripping).
createApiHandler(routeModules?, middlewareModules?, options?)
Builds a routes-only RequestHandler for API route.* files. API-folder middleware is composed into this handler. For full-stack servers, prefer apiRoutes + pipeline middleware instead of apiHandler (see below).
createFetchHandler(handler, options?)
Wraps a RequestHandler into a total Fetch handler. A null becomes options.fallback or 404. Threads env/ctx to the handler and fallback.
When options.middleware is set, the runner wraps both API dispatch and the fallback (SSR / static assets):
import { apiRoutes, middleware } from "./dist/server/api.js"; import { createFetchHandler } from "@opentf/web/server"; export default { fetch: createFetchHandler(apiRoutes, { middleware, fallback: (request, env) => env.ASSETS.fetch(request), }), };
dist/server/api.js also exports apiHandler with API-scoped middleware already composed. Use either apiHandler alone or createFetchHandler(apiRoutes, { middleware }) — not both, or middleware runs twice.
See Fetch handler for a full edge deployment walkthrough.
middlewareScopeFromPath(filePath, appDir?)
Derives the folder route a _middleware file governs (app/admin/_middleware.js → /admin).
Cookies
Standards-based Cookie / Set-Cookie helpers for middleware, API routes, and loaders. Guide: Cookies. Shipped in @opentf/web@0.16.0.
import { getCookie, getCookies, setCookie, deleteCookie, serializeCookie, } from "@opentf/web/server";
import type { CookieOptions, CookieSource, CookieTarget } from "@opentf/web/server";
| Function | Description |
|---|---|
getCookies(source) | Parse into { name: value } — Request, Headers, or raw header string. Percent-decoded; first duplicate wins (RFC 6265). |
getCookie(source, name) | One value, or undefined. |
setCookie(target, name, value, options?) | Append Set-Cookie to a Response or Headers; returns serialized value. |
deleteCookie(target, name, { path?, domain? }) | Expire with Max-Age=0 + epoch Expires. |
serializeCookie(name, value, options?) | Build a Set-Cookie header string without appending. |
CookieOptions
| Option | Description |
|---|---|
path | Defaults to "/"; pass null to omit. |
domain | Cookie domain attribute. |
maxAge | Lifetime in seconds. |
expires | Date, ISO string, or epoch ms. |
httpOnly / secure | Standard flags. |
sameSite | "Strict" | "Lax" | "None" — "None" requires secure: true. |
partitioned | CHIPS Partitioned attribute. |
A response returned from fetch or next() may have immutable headers. Wrap before calling setCookie: const wrapped = new Response(res.body, res).
Build output (dist/server/api.js)
| Export | Purpose |
|---|---|
middleware | createMiddleware runner for the whole request pipeline |
apiRoutes | route.* dispatch only — pair with pipeline middleware in createFetchHandler |
apiHandler | Standalone composed handler (app/api/* routes + app/api/_middleware.js only) |
Adapters
Fetch-native runtimes use createFetchHandler as above. Node uses the node:http adapter:
import { createServer } from "node:http"; import { apiRoutes, middleware } from "./dist/server/api.js"; import { createFetchHandler, toNodeListener } from "@opentf/web/server"; const fetch = createFetchHandler(apiRoutes, { middleware }); createServer(toNodeListener(fetch)).listen(3000);
| Export | From | Purpose |
|---|---|---|
toNodeListener(handler, opts?) | @opentf/web/server/adapters/node | Fetch handler → Node (req, res) listener. |
toWebRequest(req) | @opentf/web/server/adapters/node | Node IncomingMessage → Request. |
sendWebResponse(res, webRes) | @opentf/web/server/adapters/node | Write a Response to a Node ServerResponse. |