Forge / Linux · 17–18 Oct 2026
Benchmarks
Equal work, equal traffic. Explore the recorded measurements, understand the method and reproduce each test.
Total.js 4 0.0.99 · Express 5.3.0 · Fastify 5.12.7 · generator 8.0.0 · Meteor 3.5.2 · AdonisJS 6.21.0 · NestJS · Express 12.1.3 · Total.js 5 0.0.19
Compare the same scenario
RSS during load · MiB
Bars show the median across three repetitions. Whiskers show their minimum and maximum. Memory is process RSS in MiB; every axis starts at zero. Framework order is fixed, not a ranking.
| Framework | Baseline RSS · MiB | Load RSS · MiB | Recovery RSS · MiB | CPU · % | Latency p99 · ms | Target coverage · % | Healthy runs |
|---|---|---|---|---|---|---|---|
| Total.js 4 | 61.0 [60.9–61.1] | 159.3 [150.1–167.0] | 174.3 [172.9–174.5] | 7.9 [7.8–8.2] | 5.0 [5.0–5.0] | 100.0 [100.0–100.0] | 3/3 |
| Total.js 5 | 62.8 [62.8–63.2] | 165.7 [164.9–166.7] | 173.8 [173.1–174.3] | 8.2 [8.1–8.2] | 6.0 [6.0–6.0] | 100.0 [100.0–100.0] | 3/3 |
| Express | 74.1 [73.9–74.2] | 90.8 [90.4–91.5] | 90.9 [90.5–91.6] | 7.3 [7.1–7.6] | 5.0 [4.0–5.0] | 100.0 [100.0–100.0] | 3/3 |
| Fastify | 75.9 [75.4–76.8] | 91.2 [91.0–91.6] | 91.2 [91.0–91.6] | 4.9 [4.9–5.0] | 3.0 [3.0–3.0] | 100.0 [100.0–100.0] | 3/3 |
| Node.js http | 50.2 [50.2–50.2] | 67.9 [67.5–69.8] | 67.9 [67.7–70.0] | 4.4 [4.4–4.5] | 3.0 [3.0–3.0] | 100.0 [100.0–100.0] | 3/3 |
| Meteor | 124.3 [123.4–125.5] | 168.6 [167.6–171.1] | 169.0 [168.5–172.3] | 12.8 [12.7–12.8] | 10.0 [8.0–18.0] | 100.0 [100.0–100.0] | 3/3 |
| AdonisJS | 114.3 [113.6–115.4] | 145.3 [144.6–149.7] | 145.3 [144.6–149.6] | 10.7 [10.4–10.7] | 6.0 [6.0–7.0] | 100.0 [100.0–100.0] | 3/3 |
| NestJS · Express | 100.4 [99.3–101.3] | 129.6 [129.4–130.0] | 129.8 [129.4–130.2] | 8.5 [8.5–8.6] | 5.0 [5.0–13.0] | 100.0 [100.0–100.0] | 3/3 |
Node.jsTotal.js 4 · v24.14.1 Total.js 5 · v24.15.0 Express · v24.14.1 Fastify · v24.14.1 Node.js http · v24.14.1 Meteor · v24.15.0 AdonisJS · v24.15.0 NestJS · Express · v24.15.0 | |||||||
Values are median [minimum–maximum] across repetitions. Each RSS value is a within-phase median; sampled peaks remain in the raw data. Baseline follows readiness requests, so it is not pristine no-request memory. CPU is a percentage of one logical CPU during its sampled window. p99 describes completed requests; read it alongside errors and target coverage.
Memory through one measurement
One fresh process, sampled every second. The shaded windows identify load and recovery; warm-up and settling precede the load. RSS includes the runtime, heap and native memory. It is not live JavaScript heap.
Download the kit and prepare your computer
Run after preparing the test kit
docker run --rm --platform linux/amd64 --cpus 2 --memory 2g --memory-swap 2g --pids-limit 256 --read-only --network none --user "$(id -u):$(id -g)" --tmpfs /tmp:rw,nosuid,nodev,size=512m,mode=1777 -v "$PWD/results:/results" forge-benchmark-local node benchmarks/v2/run.cjs --frameworks totaljs --workloads hello --rates 1000 --seconds 60 --warmup 15 --rest 30 --reps 3 --connections 32 --out /results/my-run.jsonThe command uses the frozen suite for the selected run. Match hardware, container limits and background activity before comparing a rerun. Verify with the matching v2/verify.cjs after it completes.
How to read the result
Compare the same workload at the same target rate and read latency, CPU, memory, errors and target coverage together. The runtime version also matters. These measurements do not establish that one framework always uses fewer resources. Total.js 4 and 5 are shown separately; each uses its recorded implementation and runtime.
What was measured
Eight adapters, four workloads and common targets of 100 and 1,000 requests/s: 192 measurements. Each measurement starts a fresh production process: response check, 1.5 s baseline, 15 s warm-up, 6 s settling, 60 s load and 30 s recovery. All three repetitions are retained. Adapters were measured sequentially. Successful response bodies are checked before and after load; the generator requests up to 32 connections.
Total5 uses a compact NEWSCHEMA and direct controller for JSON validation; small JSON responses use the controller directly. The comparison contains complete recorded scenarios, never the best numerical result. Meteor uses its server-only production bundle; NestJS uses its Express adapter. Native validators cover the measured body; rejection and coercion behavior can differ between frameworks.
Environment
Shared Linux x64 host, Intel Core i7-6700. Every measurement container was limited to 2 logical CPUs and 2 GiB, without external network or published ports. The generator and simulated dependency shared the container. Node 24.14.1 was used for Total4, Express, Fastify and Node http; Node 24.15.0 for Total5, Meteor, AdonisJS and NestJS, matching the Meteor bundle runtime. Runtime versions are preserved in the original data: differences cannot be attributed to the framework alone. Production services remained on the host.
What these results do not establish
This is a fixed-rate, closed-loop experiment, not a maximum-throughput ranking. Reaching the configured target does not prove how an adapter handles independent arrivals or saturation. There was some CPU quota throttling, recorded in the review. Three short repetitions do not establish long-term stability, absence of memory leaks, production security, Flow behavior or Raspberry Pi memory use. No garbage collection was forced. RSS may remain allocated after traffic stops.
A cell is healthy when completions reach at least 95% of target rate × generator duration, p99 is at most 250 ms and error, timeout and non-2xx counts are zero. This threshold is not a claim of equal maximum capacity.
Original evidence
Complete framework comparison
Integrity and environment review · SHA-256 checksums · JSON · Markdown
Reproduce and inspect
Use an empty directory with Docker (Linux containers), curl and tar. Node.js is not required on the host: both the checksum check and test suite use Node 24.15.0 inside Docker. The first build downloads dependencies and compiles Meteor, which can take several minutes. Preparation needs internet; the test containers have no external network. Each measured scenario takes about six minutes.
The kit runs all eight adapters on Node v24.15.0. Published measurements retain their original runtimes, shown in the table.
Download the complete test kit
1. Download and prepare. The link contains the runner, adapters and dependency locks; no Forge installation or repository clone is required. In an empty directory, run the following commands. If you downloaded with the button, keep the filename sources.tar.gz and skip curl.
curl --fail --location --output sources.tar.gz 'https://forge.totaljs.es/benchmarks/node-24.15.0/sources.tar.gz' &&
docker run --rm --platform linux/amd64 --network none -v "$PWD:/kit:ro" node:24.15.0-bookworm node -e 'const fs=require("node:fs"),crypto=require("node:crypto");if(crypto.createHash("sha256").update(fs.readFileSync("/kit/sources.tar.gz")).digest("hex")!=="65801cd865a3fc961e69ba8df421ed198acdc399d29934714fb954e4de1db651")throw Error("SHA-256 mismatch");' &&
tar -xzf sources.tar.gz &&
docker build --platform linux/amd64 -t forge-benchmark-local . &&
mkdir -p results
2. Check the installation (about 15–30 seconds). This short test produces smoke.json and verifies its recorded inputs; it is not a publishable performance result.
docker run --rm --platform linux/amd64 --cpus 2 --memory 2g --memory-swap 2g --pids-limit 256 --read-only --network none --user "$(id -u):$(id -g)" --tmpfs /tmp:rw,nosuid,nodev,size=512m,mode=1777 -v "$PWD/results:/results" forge-benchmark-local node benchmarks/v2/run.cjs --smoke --frameworks totaljs --workloads hello --rates 1000 --out /results/smoke.json
docker run --rm --platform linux/amd64 --cpus 2 --memory 2g --memory-swap 2g --pids-limit 256 --read-only --network none --user "$(id -u):$(id -g)" --tmpfs /tmp:rw,nosuid,nodev,size=512m,mode=1777 -v "$PWD/results:/results" forge-benchmark-local node benchmarks/v2/verify.cjs /results/smoke.json --allow-smoke
3. Run and verify the selected measurement. Results are saved in results/my-run.json inside the selected benchmarks directory. The runner refuses to overwrite existing output: use another filename for a repeat. Verification checks completeness and inputs, not equality with the published figures.
docker run --rm --platform linux/amd64 --cpus 2 --memory 2g --memory-swap 2g --pids-limit 256 --read-only --network none --user "$(id -u):$(id -g)" --tmpfs /tmp:rw,nosuid,nodev,size=512m,mode=1777 -v "$PWD/results:/results" forge-benchmark-local node benchmarks/v2/run.cjs --frameworks totaljs --workloads hello --rates 1000 --seconds 60 --warmup 15 --rest 30 --reps 3 --connections 32 --out /results/my-run.json
docker run --rm --platform linux/amd64 --cpus 2 --memory 2g --memory-swap 2g --pids-limit 256 --read-only --network none --user "$(id -u):$(id -g)" --tmpfs /tmp:rw,nosuid,nodev,size=512m,mode=1777 -v "$PWD/results:/results" forge-benchmark-local node benchmarks/v2/verify.cjs /results/my-run.json