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

TypeScript
import type {
  ApiHandler,
  Middleware,
  MiddlewareContext,
  MiddlewareRunner,
  ApiContext,
  RouteParams,
  RequestHandler,
} from "@opentf/web/server";
TypeDescription
ApiHandler(request, context) => Response — a route.* method export (GET, POST, …).
Middleware(request, context, next) => Response — a _middleware default export.
MiddlewareContextPre-routing context: url, locals, env?, ctx? — no params/query yet.
MiddlewareRunnerThe 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

FieldTypeDescription
paramsRouteParamsDynamic segments ([param]string, [...rest]string[]).
queryRecord<string, string>Parsed query string.
urlURLParsed request URL.
localsRecord<string, unknown>Mutable bag; pipeline middleware writes, handlers and loaders read.
envunknownWorkers 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):

JavaScript
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),
  }),
};
Don't double-wrap

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.

JavaScript
import {
  getCookie,
  getCookies,
  setCookie,
  deleteCookie,
  serializeCookie,
} from "@opentf/web/server";
TypeScript
import type { CookieOptions, CookieSource, CookieTarget } from "@opentf/web/server";
FunctionDescription
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

OptionDescription
pathDefaults to "/"; pass null to omit.
domainCookie domain attribute.
maxAgeLifetime in seconds.
expiresDate, ISO string, or epoch ms.
httpOnly / secureStandard flags.
sameSite"Strict" | "Lax" | "None""None" requires secure: true.
partitionedCHIPS Partitioned attribute.
Immutable response headers

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)

ExportPurpose
middlewarecreateMiddleware runner for the whole request pipeline
apiRoutesroute.* dispatch only — pair with pipeline middleware in createFetchHandler
apiHandlerStandalone composed handler (app/api/* routes + app/api/_middleware.js only)

Adapters

Fetch-native runtimes use createFetchHandler as above. Node uses the node:http adapter:

JavaScript
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);
ExportFromPurpose
toNodeListener(handler, opts?)@opentf/web/server/adapters/nodeFetch handler → Node (req, res) listener.
toWebRequest(req)@opentf/web/server/adapters/nodeNode IncomingMessageRequest.
sendWebResponse(res, webRes)@opentf/web/server/adapters/nodeWrite a Response to a Node ServerResponse.