Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
# - uses: denoland/setup-deno@v2
- run: mkdir -p "$HOME/.deno/bin" && curl -fsSL https://github.com/denoland/deno/releases/download/v2.7.12/deno-x86_64-unknown-linux-gnu.zip -o /tmp/deno.zip && unzip -q /tmp/deno.zip -d "$HOME/.deno/bin" && echo "$HOME/.deno/bin" >> "$GITHUB_PATH"
- run: pnpm install
- run: pnpm vitest run --coverage test/deno.test.ts test/deno-max-body-size.test.ts test/url.test.ts
- run: pnpm vitest run --coverage test/deno.test.ts test/deno-max-body-size.test.ts test/url.test.ts test/cluster.test.ts
- run: pnpm add -D undici@^7.25.0
- run: deno run test:node-compat:deno
tests_deno_node_adapters:
Comment thread
OskarLebuda marked this conversation as resolved.
Expand All @@ -69,7 +69,7 @@ jobs:
with: { node-version: lts/*, cache: pnpm }
- uses: oven-sh/setup-bun@v2
- run: pnpm install
- run: pnpm vitest run --coverage test/bun.test.ts test/bun-max-body-size.test.ts test/url.test.ts
- run: pnpm vitest run --coverage test/bun.test.ts test/bun-max-body-size.test.ts test/url.test.ts test/cluster.test.ts
- run: bun run test:node-compat:bun
- run: bun run test:node-adapters:bun
publish:
Expand Down
10 changes: 10 additions & 0 deletions docs/1.guide/05.options.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ Enabling this option allows multiple processes to bind to the same port, which i
> [!NOTE]
> Despite Node.js built-in behavior that has `exclusive` flag enabled by default, srvx uses non-exclusive mode for consistency.

### `cluster`

Run multiple server processes sharing the same port.

- `true`: number of workers from the `SRVX_WORKERS` environment variable, or CPU cores.
- `number`: number of worker processes (a positive integer; `0` disables cluster mode).
- `false`: explicitly disable cluster mode (also ignores `SRVX_WORKERS`).
Comment thread
OskarLebuda marked this conversation as resolved.

:read-more{to="/guide/cluster"}

### `silent`

If enabled, no server listening message will be printed (enabled by default when `TEST` environment variable is set).
Expand Down
3 changes: 3 additions & 0 deletions docs/1.guide/10.cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ SERVE MODE
# srvx serve [options]
$ srvx serve --entry ./server.ts # Start development server
$ srvx serve --prod # Start production server
$ srvx serve --prod --cluster # Production server with one worker per CPU core
$ srvx serve --port=8080 # Listen on port 8080
$ srvx serve --host=localhost # Bind to localhost only
$ srvx serve --static=./dist # Serve static files (no entry needed)
Expand Down Expand Up @@ -71,6 +72,7 @@ SERVE OPTIONS
--host, --hostname <host> Host to bind to (default: all interfaces)
-s, --static <dir> Serve static files from the specified directory (default: public)
--prod Run in production mode (no watch, no debug)
--cluster [N] Run N server processes (default: CPU cores, requires --prod)
--import <loader> ES module to preload
--tls Enable TLS (HTTPS/HTTP2)
--cert <file> TLS certificate file
Expand All @@ -91,6 +93,7 @@ ENVIRONMENT

PORT Default port to listen on
HOST Default host to bind to
SRVX_WORKERS Number of cluster workers (enables cluster mode)
NODE_ENV Set to production for production mode.
```

Expand Down
70 changes: 70 additions & 0 deletions docs/1.guide/12.cluster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
icon: ri:stack-line
---

# Cluster Mode

> Run multiple server processes sharing the same port

A single JavaScript process uses one CPU core. In production, cluster mode lets srvx spawn multiple worker processes that share the same port, so incoming requests are load balanced across all CPU cores - no external process manager needed.

The main process becomes a lightweight supervisor. It never handles requests itself; it spawns workers (re-executing the same server entry), restarts crashed workers (with exponential backoff) and forwards shutdown signals so each worker can close gracefully.

## Usage

**CLI:**

```bash
# One worker per CPU core
srvx serve --prod --cluster

# Exact number of workers
srvx serve --prod --cluster=4
```

> [!NOTE]
> Cluster mode is production-only in the CLI (in dev mode a single process with watcher is used).

**Programmatic:**

```js
import { serve } from "srvx";

serve({
cluster: true, // or an exact number of workers
fetch: () => new Response(`👋 Hello from worker ${process.env.SRVX_CLUSTER_WORKER}`),
});
```

**Environment variable:**

Setting `SRVX_WORKERS` enables cluster mode without touching code or CLI flags (handy in Docker / Kubernetes):

```bash
SRVX_WORKERS=4 srvx serve --prod
```

An explicit `cluster: <number>` option takes precedence over `SRVX_WORKERS`. Use `cluster: false` (or `--cluster=false` in the CLI) to disable cluster mode entirely, including `SRVX_WORKERS`.

## Worker processes

Each worker re-executes the same server entry. Workers can be detected via the `SRVX_CLUSTER_WORKER` environment variable, which contains the worker index (starting at `"0"`):

```js
if (process.env.SRVX_CLUSTER_WORKER) {
// Running as a cluster worker
}
```

If a worker crashes, the supervisor restarts it automatically (with exponential backoff). If a worker repeatedly fails during startup (e.g. port in use, invalid TLS config), the supervisor gives up after 3 attempts and exits with a non-zero code.

On `SIGINT`/`SIGTERM`, the supervisor forwards the signal to all workers for graceful shutdown and exits once all of them have finished.

## Runtime support

- **Node.js**: workers share the listening socket via [`node:cluster`](https://nodejs.org/api/cluster.html), so load balancing works on every platform. The distribution follows Node's own scheduling policy: round-robin everywhere except Windows, where Node defaults to `SCHED_NONE` and lets the OS pick the worker (override with `NODE_CLUSTER_SCHED_POLICY=rr`).
- **Bun** and **Deno**: workers bind the port with `SO_REUSEPORT` and the kernel load balances between them. Kernel load balancing is **Linux only** - on other platforms a single supervised worker is started instead (crash restarts still work).
- **Serverless runtimes** (Cloudflare, AWS Lambda, ...): the platform scales processes itself, the `cluster` option is ignored.

> [!IMPORTANT]
> Cluster mode requires a fixed port (`port: 0` is not supported).
Loading
Loading