DEV Community

Volodymyr Diachenko
Volodymyr Diachenko

Posted on

I Benchmarked NestJS GraphQL: Express vs Fastify vs Mercurius — Here's What Actually Won

TL;DR: Under sustained load, Mercurius is the clear winner, outperforming the other setups by 60–89% across all tested scenarios. But low-concurrency benchmarks tell a different story. The “best” choice depends heavily on how you test.

Live Dashboard | GitHub


The Setup

I benchmarked three NestJS GraphQL server configurations using the same schema, resolvers, and in-memory data generators. The only differences were the HTTP transport and GraphQL engine.

Server HTTP Transport GraphQL Engine
Express + Apollo @nestjs/platform-express Apollo Server 4
Fastify + Apollo @nestjs/platform-fastify Apollo Server 4
Fastify + Mercurius @nestjs/platform-fastify Mercurius 14

All tests were run with k6 in sequential mode — one server at a time, using the full machine. Each run included 500 warmup requests, and every GraphQL response was validated.

I used two benchmark profiles:

  • Quick: 10 VUs, ~3 minutes

  • Standard: 50 VUs, ~20 minutes


Standard Results (50 VUs)

                        Express      Fastify        Mercurius
Health (light):         6,491        8,729 (+34%)   12,238 (+89%)
Single User (medium):   4,328        5,128 (+18%)    7,772 (+80%)
Paginated (heavy):      1,423        1,501  (+6%)    2,549 (+79%)
Deep Nested (extreme):    267          272  (+2%)      496 (+86%)
Mutation (medium):      5,598        7,000 (+25%)    8,930 (+60%)
Enter fullscreen mode Exit fullscreen mode

Mercurius came out on top in every scenario.

The most interesting result was not that Mercurius won — it was how little Fastify + Apollo improved over Express on heavy queries. In the more expensive scenarios, the gap was only around 2–6%, much smaller than I expected.

Throughput comparison chart for Express, Fastify, and Mercurius across five GraphQL scenarios, showing Mercurius leading in every test


The Interesting Part: Quick vs Standard

Same code, same machine, different load profile — different results:

Mutation (quick, 10 VUs):    Mercurius +2%    (barely wins)
Mutation (standard, 50 VUs): Mercurius +60%   (dominates)
Enter fullscreen mode Exit fullscreen mode

Mercurius scales better as concurrency increases. A low-concurrency benchmark doesn’t tell the full story.


Reality Check

These benchmarks focus on framework overhead, not full application behavior.

There’s no database, no Redis, no external API calls, and no network latency. Everything runs against in-memory data so the comparison isolates the framework stack as much as possible.

In a real application, resolvers often spend most of their time elsewhere:

  • PostgreSQL queries: +5–50ms

  • external API calls: +50–200ms

  • uncached or complex operations: often much more

In that context, framework overhead may be only a small fraction of total latency.

So while framework choice does matter, it matters most when:

  • your app is already well optimized

  • throughput at scale is important

  • framework overhead is one of the remaining bottlenecks

If your API feels slow today, your first win probably won’t come from switching frameworks. It will usually come from fixing query design, eliminating N+1 problems with DataLoader, improving caching, or reducing database and external API latency.


Recommendation

Mercurius — if you want the best throughput and p99 latency under load. In these tests, it was the clear winner.

Express + Apollo — if you want the most familiar ecosystem or are optimizing for compatibility and fast prototyping.

Fastify + Apollo — if you need Apollo-specific features like federation and still want a moderate boost over Express (+6–34%).


Try It Yourself

git clone https://github.com/vovadyach/gql-bench
cd gql-bench
npm run setup           # ~20 min
npm run bench:quick     # ~3 min
Enter fullscreen mode Exit fullscreen mode

Or explore the Live Dashboard — toggle frameworks, switch profiles, dive into scenarios.


What's Next

I’m extending this benchmark beyond Node.js to compare Go, C#, and Java against the same workload.

The NestJS results were already interesting. The cross-language comparison should be even more revealing.

Follow the repo if you want to see the next round of results.


Built with k6, NestJS 10, Apollo Server 4, Mercurius 14. Dashboard: Next.js + shadcn/ui + Recharts.

Top comments (0)