Skip to content

Benchmarks

GET /users/:id JSON read

  1. platform-express93k
  2. platform-fastify116k
  3. @nestbun/platform119k
  4. Bun.serve · no framework158k

POST /users JSON write, body parsed

  1. platform-express72k
  2. platform-fastify92k
  3. @nestbun/platform98k
  4. Bun.serve · no framework154k
Requests per second, higher is better. One Nest controller, three adapters, one runtime. Apple M-series laptop, Bun 1.4, 64 connections, 3-second runs, load generator in a separate process. bun run bench reproduces it.
View as table
AdapterGET /users/:idPOST /users
platform-express93,449 req/s71,763 req/s
platform-fastify116,358 req/s91,533 req/s
@nestbun/platform119,022 req/s98,124 req/s
Bun.serve157,694 req/s153,660 req/s

One Nest controller with two routes:

@Get(':id') one(@Param('id') id: string) { return { id, name: 'Ada', email: 'ada@example.com', roles: ['admin', 'dev'] }; }
@Post() create(@Body() body: unknown) { return { created: body }; }

The same module is booted three times, once per adapter, all on Bun. Two baselines bracket the results: raw Bun.serve returning the same JSON with no framework, and the adapter’s router alone without Nest.

  • Load is generated by a separate Bun process (bench/load.ts) so the server is not competing with its own client for CPU. If oha is installed it is used instead.
  • 64 concurrent connections, 3-second runs, keep-alive.
  • Apple M-series laptop, Bun 1.4, NestJS 12.1, logger disabled.
Terminal window
bun run bench # everything
BASELINES=0 ADAPTERS=bun,fastify bun run bench
DURATION=10 CONCURRENCY=128 bun run bench
  • The gap between raw Bun.serve and any adapter is Nest itself: DI resolution, the execution context, guards and pipes, exception mapping. Nothing an adapter can remove.
  • The gap between adapters is the platform layer: request and response objects, routing, body parsing. That is where the Bun adapter wins by avoiding node:http emulation.
  • The POST scenario exercises body parsing. An early version of the adapter read bodies through body.getReader() and managed 26k req/s; switching to arrayBuffer() for bodies with a Content-Length took it to 98k.

Numbers vary by machine. The ordering has been stable across runs.