NestJS — clustering

The housed pool's second front door (Node ≥ 26): a dedicated listener where the main thread accepts TCP connections and hands each socket to a housed worker unparsed — no parsing, no proxy hop, no serialization on the API thread.

Wiring

main.tsbackend
// src/main.ts — cluster the housed pool onto its own listener (Node ≥ 26)
import { getAtollPool } from '@atolljs/nestjs';
import { createHttpCluster } from '@atolljs/node/http';

const pool = getAtollPool('housed');
if (pool) {
  const transferPort = Number(process.env.TRANSFER_PORT ?? port + 1);
  createHttpCluster({
    pool,
    port: transferPort,
    onListen: () => log.log(`housed api (clustered) → :${transferPort}`),
  });
  // Below Node 26 the call logs a notice and returns null — no
  // capability check needed.
}

The worker side needs nothing — serveHttp in housed.worker.ts already accepts transferred sockets alongside its internal gateway port, so the same housed Nest app serves both entries.

Why a second port

Clustering routes per connection: the accepting thread never reads request bytes, so it can't split the app's port by URL path. /api/housed/* on the main port keeps using proxyToWorker (one origin for clients); the cluster listener is the zero-parse fast path for anything that can reach it.

Stickiness

A transferred socket pins for life, but separate connections round-robin — multi-connection session flows (socket.io's polling→upgrade, an HTTP call preceding a WS connection on the same worker) need route: stickyByAddress(), which hashes the client address onto a fixed worker. Details under Node.js → Clustering.

Live example

On Node ≥ 26, examples/nestjs serves the housed API clustered on :3101 — http://localhost:3101/api/housed/incidents/whoami answers with a worker's threadId while the API thread never touched the request.