Load Test Benchmarks — Go vs NestJS vs Express

Same news platform, built three times, deployed on one shared VPS — real Artillery load-test results, not a framework listicle.

The Three Backends

Infrastructure

One shared VPS — 2 vCPU, 8GB RAM, 100GB NVMe — with nginx in front, all three backends (plus 3 unrelated apps) running via PM2 on the same two cores. MySQL for NestJS/Express, Postgres + pgvector for Go. Go's search is real, working semantic search: a locally-running Ollama instance (nomic-embed-text) embeds verified articles, served back via a cosine-distance nearest-neighbor query.

Round 1 — Normal Load

1→20 requests/second ramp over 60 seconds, same endpoint (GET /v1/feed) on all three.

Backendp50 latencySuccess rate
Go34ms6%
NestJS39ms50%
Express39ms100%
In plain terms: "p50 latency" is roughly how long you'd typically wait for someone to pick up the phone — around 34–39 thousandths of a second for all three, so no real difference in speed. "Success rate" is how many callers got through vs. got turned away with a "call back later." Go turned away 94 out of 100 callers here, NestJS turned away half, Express turned away none — but that's not Express being faster or better. Each backend just has its own house rule for how many requests it accepts before saying "we're full for now," and Go's rule is simply the strictest of the three, NestJS's in the middle, Express's the most lenient. The spread above is entirely down to those different rate limits, not raw speed.

Round 2 — Heavy Load

Ramped to 300 requests/second sustained — well past all three rate limiters.

BackendTimeout rate429 (fast reject)200 OK
Go98.4%1.5%0.2%
Express98.2%0%1.8%
NestJS98.5%0%1.5%
In plain terms: imagine calling a restaurant to order. "200 OK" = they picked up and took the order. "429" = they picked up and said "we're slammed, call back later" — a fast, honest no. "Timeout" = the phone just rings forever, no answer at all. At 300 calls/second, about 98 out of 100 calls to any of the three got no answer whatsoever — that's everyone struggling equally, most likely because the test itself couldn't place that many calls cleanly, not because any one backend is weak. The one difference: Go still managed to say "call back later" to a few callers under this pressure; the other two never did — every call to them either connected or rang forever, nothing in between. Suggestive of Go staying responsive a little further into overload, not proof of a winner from a single run.

Takeaway

At normal scale, framework speed wasn't the differentiator — deployment simplicity (Go), team-fit via familiar patterns (NestJS), and control over the data layer (Express) were. Under extreme, client-saturating load, none of the three "won" outright, though Go showed the only sign of graceful degradation.