runtime:http
An HTTP/1.1 and HTTP/2 server: serve((request) => response). The handler takes a web Request and returns a web Response — the same Fetch API objects fetch uses.
Exposed as an ES module under the runtime: scheme. Status: Available.
A handler error or a non-Response return becomes a 500. Request and response bodies stream with backpressure; string/byte bodies use the buffered fast path (Content-Length). HTTP/1.1 and HTTP/2 are both served on one port — ALPN over TLS, h2c by prior knowledge on cleartext — and the version never reaches the handler. TLS terminates in-process with secureTransport: "on". See the HTTP server guide for patterns and examples.
Functions
| Function | Type | Description | Example |
|---|---|---|---|
serve(handler) | (Handler) => Server | Start a server on an ephemeral port (read it from server.addr). handler is (request) => response. | serve((req) => new Response("hi")) |
serve(options, handler) | ({ hostname?, port?, secureTransport?, cert?, key?, alpn?, timeouts? }, Handler) => Server | Start a server bound to options. hostname defaults to 0.0.0.0; port 0 picks an ephemeral port. secureTransport: "on" terminates TLS (needs inline PEM cert + key; alpn defaults to ["h2", "http/1.1"]). timeouts bounds connections that stall — see below. | serve({ port: 443, secureTransport: "on", cert, key }, handler) |
Serve
import { serve } from "runtime:http"; // The handler takes a web Request and returns a web Response — // the same Fetch API objects fetch() uses. const server = serve({ port: 8080 }, async (request) => { const url = new URL(request.url); if (url.pathname === "/echo") { return new Response(await request.text(), { status: 200 }); } return Response.json({ method: request.method, path: url.pathname }); }); const { hostname, port } = await server.addr; console.log(`listening on http://${hostname}:${port}`);
Timeouts
timeouts bounds connections that are idle or stalled. A request in flight, a body still arriving, and a response still streaming are never interrupted.
| Key | Default | Bounds |
|---|---|---|
handshake | 10000 | Accept → ready to carry requests: the TLS handshake, and the wait for the first byte |
headerRead | 30000 | A request head arriving in full; on HTTP/1.1 also the idle keep-alive limit |
h2KeepAlive | 20000 | PING probes on an idle HTTP/2 connection; a dead peer goes within twice this |
Each is a number of milliseconds. null disables one, omitting it keeps the default, and a non-number is a TypeError at the call rather than a bound port.
serve({ port: 8080, timeouts: { headerRead: 5000, h2KeepAlive: null } }, handler);
Bodies
| Direction | ReadableStream | string / bytes |
|---|---|---|
| Request | request.body pulls chunks as they arrive | request.text() / .json() / .arrayBuffer() materialize the full body |
| Response | Chunked transfer-encoding, backpressured | Content-Length buffered fast path |
Lifecycle
serve() returns a Server immediately; the accept loop runs in the background.
const server = serve(handler); // ephemeral port const { port } = await server.addr; // resolved once listening // … handle requests … await server.stop(); // stop accepting; finished resolves
Server
| Member | Type | Description |
|---|---|---|
addr | Promise<{ hostname, port }> | The bound address; resolves once the server is listening. |
finished | Promise<void> | Resolves when the accept loop has ended (after stop()). |
stop() | Promise<void> | Stop accepting and shut down; resolves once stopped. |
Errors
| Error | When |
|---|---|
TypeError | The serve() handler isn't a function; secureTransport is "on" without both cert and key, or is a value other than "on"/"off"; or a Request / Response is built with an invalid init (the Fetch API's own checks). |
Error | code "ERR_TLS" — the cert or key could not be parsed. Raised by serve() itself, so a bad cert never becomes a port that rejects every handshake. |
DOMException | name "NotAllowedError" — the NetListen capability is not granted. |
See also
HTTP server guide — routing, streaming bodies, lifecycle, HTTPS