DEV Community

Volodymyr Diachenko
Volodymyr Diachenko

Posted on

Apollo vs Mercurius vs Yoga: I Benchmarked 10 Node.js GraphQL Setups (With and Without NestJS)

Choosing a GraphQL server for Node.js? I benchmarked Apollo, Mercurius and Yoga on Express, Fastify and plain node:http, each with and without NestJS: 10 setups, same schema, same queries.

This is a follow-up to my NestJS GraphQL benchmark (Express vs Fastify, Apollo vs Mercurius). The most common question there was: "How much does NestJS itself cost? What if I drop it?" So this time I added:

  • NestJS + GraphQL Yoga
  • The same stacks without NestJS: Express + Apollo, Fastify + Apollo, Fastify + Mercurius, Fastify + Yoga
  • Yoga on Express and Yoga standalone on plain node:http

…and re-ran everything.

TL;DR

  • The GraphQL engine matters most. Mercurius did 1.3–1.8× the throughput of Apollo in every query scenario, with about half the p99 latency on nested queries. Yoga landed in between on real queries.
  • The HTTP framework matters mostly for tiny queries. With Apollo, Fastify beat Express by 18–29% on health but only ~3% on deep-nested. For Yoga, standalone, Express and Fastify were within a few percent on real queries.
  • NestJS adds no measurable overhead. Each NestJS server is within −6% to +5% of the same stack without it, and which one wins flips from scenario to scenario.
  • Mutations are the same everywhere (~9,000–9,400 req/s).

📊 Interactive dashboard: https://gql-bench.vercel.app/
💻 Code (run it yourself): https://github.com/vovadyach/gql-bench

What was tested

Every server implements the same schema, data and resolvers. Only the stack changes.

NestJS 10 Direct (no NestJS)
Express + Apollo Express + Apollo
Fastify + Apollo Fastify + Apollo
Fastify + Mercurius Fastify + Mercurius
Fastify + Yoga Fastify + Yoga
Express + Yoga
Yoga standalone (node:http)

Scenarios:

Scenario What it is
health Tiny query: pure framework + GraphQL engine overhead
single-user 1 user + posts + comments
user-list 10 users, nested
deep-nested 5 × 10 × 8 nested objects + some CPU work (~90 KB responses)
mutation Write with body parsing
Mixed traffic Production-like query mix: 30% health, 25% single-user, 20% mutation, 15% user-list, 10% deep-nested

Results

Requests per second, median of 3 full runs (higher is better):

Server health single-user user-list deep-nested mutation mixed
NestJS Fastify + Mercurius 30,631 13,047 4,397 902 9,065 6,862
Direct Fastify + Mercurius 29,489 12,889 4,226 887 9,004 6,769
Direct Yoga standalone 20,994 11,050 3,562 731 9,072 5,436
Direct Express + Yoga 19,575 11,109 3,625 730 9,162 5,388
Direct Fastify + Yoga 17,854 10,525 3,548 731 8,988 5,214
NestJS Fastify + Yoga 16,966 10,084 3,533 753 8,984 5,151
NestJS Fastify + Apollo 21,937 10,191 2,831 493 9,444 3,974
Direct Fastify + Apollo 21,075 9,560 2,781 484 9,423 4,055
NestJS Express + Apollo 17,032 9,080 2,703 480 9,378 3,706
Direct Express + Apollo 17,856 9,036 2,706 472 9,145 3,832

p99 latency on deep-nested: 68 ms for Mercurius vs 123 ms for Fastify + Apollo.

NestJS vs direct, pair by pair

Throughput of the direct version relative to the NestJS version:

Pair health single-user user-list deep-nested mutation mixed
Express + Apollo +4.8% −0.5% +0.1% −1.8% −2.5% +3.4%
Fastify + Apollo −3.9% −6.2% −1.8% −1.9% −0.2% +2.0%
Fastify + Mercurius −3.7% −1.2% −3.9% −1.6% −0.7% −1.4%
Fastify + Yoga +5.2% +4.4% +0.4% −3.0% 0.0% +1.2%

No consistent winner. NestJS adds dependency injection and a resolver layer, but once a request reaches the GraphQL engine, that layer costs about as much as run-to-run noise.

Methodology

  • Apple M1 Max, Node.js 24, single process per server, one server at a time
  • k6, ramping to 50 virtual users, 30 s sustained load per scenario
  • 3,000 warmup requests per server (discarded), 5 s cooldown between servers
  • Every GraphQL response validated; response sizes verified identical across servers
  • All runs passed thresholds (<1% HTTP failures, <1% GraphQL errors)
  • 3 full runs, median reported

Limits: in-memory data (no database), one machine, single process. Absolute numbers will differ on your hardware; the ratios between servers are the useful part. With a real database, these differences shrink because I/O dominates.

So what should you use?

Not using NestJS?

  • Fastest: Fastify + Mercurius, in every query scenario.
  • Like Yoga? Its HTTP layer barely matters on real queries: standalone, on Express or on Fastify all landed within a few percent.
  • On Apollo? Prefer Fastify over Express for light queries, and expect Apollo to fall behind as queries get more nested.

Using NestJS?

  • Need more throughput? Switch the driver to @nestjs/mercurius on Fastify; it gave the biggest gain in every query scenario.

Top comments (0)