[]previous and next page

The Node server

serve(handler, options) starts Node's http server around any object with fetch(request, platform).

For agentsUpdated

serve(handler, options) starts Node's http server around any object with fetch(request, platform). It turns each request into a Web Request with a streaming body and a signal that aborts when the client disconnects, streams the response back with backpressure, compresses text responses, answers static files itself, and shuts down gracefully. dusk start runs the production build with the Node adapter, and dist/client is served as static files. To set the options yourself, write a serve.mjs file like the one below and run it with node serve.mjs.

serve.mjsjs
import { fileURLToPath } from "node:url";
import { serve } from "@shadow-js/dusk/node";
import { handler } from "./dist/server/entry.js";

const server = await serve(handler, {
  port: Number(process.env.PORT ?? 3000),
  host: process.env.HOST ?? "127.0.0.1",
  static: { dir: fileURLToPath(new URL("./dist/client/", import.meta.url)) },
});
console.log(`listening on ${server.url}`);

The same file in TypeScript. dist/server/entry.js has no type declarations, so the import is typed by hand:

serve.tsts
import { fileURLToPath } from "node:url";
import { serve } from "@shadow-js/dusk/node";
import type { Handler } from "@shadow-js/dusk/server";

const entry = (await import(new URL("./dist/server/entry.js", import.meta.url).href)) as {
  handler: Handler;
};

const server = await serve(entry.handler, {
  port: Number(process.env["PORT"] ?? 3000),
  host: process.env["HOST"] ?? "127.0.0.1",
  static: { dir: fileURLToPath(new URL("./dist/client/", import.meta.url)) },
});
console.log(`listening on ${server.url}`);

Running it on the built scratch app prints listening on http://127.0.0.1:4401 with PORT=4401. The server listens on 127.0.0.1 unless host says otherwise; in a container use 0.0.0.0 (the Dockerfile below sets HOST). An option name that serve does not know throws a TypeError that suggests the closest one.

ServeOptions Default Meaning
port 3000 0 picks a free port; server.port reports it
host "127.0.0.1" interface to listen on
static none false or StaticOptions; served before the handler
compress on false, or CompressOptions; br, zstd (where node:zlib has it) or gzip by Accept-Encoding, flushed per chunk so streamed pages stay progressive
earlyHints true send 103 Early Hints for page requests (Early Hints)
trustProxy false believe X-Forwarded-Proto and X-Forwarded-Host (proxy)
gracefulShutdownMs 10000 how long a shutdown waits for requests in flight
shutdownSignals ["SIGTERM", "SIGINT"] signals that start a shutdown; false registers none
signalTarget process where the signal listeners are registered
exitOnSignal true call exit(0) after a signal-triggered shutdown
exit process.exit called with the exit code
headersTimeoutMs 30000 time to receive the request headers
requestTimeoutMs 120000 time to receive the whole request
keepAliveTimeoutMs 5000 idle time before a keep-alive connection closes
maxHeaderBytes 16384 largest header block
workers none n or "auto": run that many server processes that share the port (Using every core); absent or 1 is one process
supervise see below restart delays, the crash-loop breaker and the restart signal of the worker pool
tls none https server options; server.url then starts with https://
onError console.error failures that cannot reach the client (a body that broke mid-stream)

serve returns a ServerHandle: role, port, host, url, the Node server, draining and close(). Static files are answered before the handler runs, so they do not carry the handler's security headers, host check or request id. If that matters, serve dist/client from a CDN or the proxy and give the proxy the headers.