NestJS Logo

Nest vs Fastify, Express and Hono

Fastify, Express and Hono route requests to your handlers, and you choose or write what goes around them. Nest organizes the whole backend, with modules, dependency injection, guards, pipes and first-party packages, and it runs on top of Express, Fastify or Node's own node:http. So Nest doesn't replace Fastify or Express: they can be what it runs on. This page helps you choose.

When to use which#

What you're buildingBetter fitWhy
A backend that will grow: more modules, more developers, a database, background jobsNestModules, dependency injection, guards and pipes give it one structure, and services are easy to test
An enterprise-grade system, with many teams and a long-lived codebaseNestEvery team works in the same structure, and the core team offers enterprise support
A small API with authentication, validation and a databaseNest or Fastifynest new --minimal starts from one file, and first-party packages add the rest. Fastify does the same with plugins and less code around each route
Microservices over Kafka, RabbitMQ, NATS or gRPCNestThe transports are first-party and share one programming model
A service that will stay a few routes, such as a proxy or a webhook receiverFastify or HonoThere's nothing to organize
An API on Cloudflare Workers or another edge runtimeHonoIt's built on Web Standards and runs there natively. See Cloudflare Workers
A Next.js frontend sharing route types with its API, with no code generationHonoIts RPC client infers types from the routes. With Nest, a shared package holds the schemas and types instead
An Express application that already worksExpress, or Nest one route at a timeMigrating from Express runs both side by side
The highest request rate on routes that do very littleFastifyIt's a few percent ahead of Nest. See Performance

What comes with each#

Modules and dependency injectionBuilt inEncapsulated plugins, no containerYou design itYou design it
Request validationBuilt-in pipes, with classes or any Standard Schema libraryJSON Schema on each route, built inAdd a library@hono/zod-validator or similar
Authentication@nestjs/authentication and guards@fastify/jwt and other pluginsPassport or your own middlewarehono/jwt and other middleware
OpenAPI@nestjs/swagger@fastify/swaggerAdd a library@hono/zod-openapi
WebSocketsGateways, with Socket.IO or ws@fastify/websocketAdd a libraryRuntime helpers such as @hono/node-ws
Background jobs@nestjs/bullmqAdd a libraryAdd a libraryAdd a library
Microservice transports@nestjs/microservicesAdd a libraryAdd a libraryAdd a library
StreamingReturn a Response or a streamSend a stream from a handlerWrite to the responsestreamSSE and other helpers
Testing@nestjs/testing, which can replace providersfastify.inject()Add Supertestapp.request()
Types shared with a frontendA shared packageAdd a libraryAdd a libraryRPC client built in

How small a Nest application can be#

nest new my-api --minimal generates an application whose code is one file, src/main.ts:

src/main.ts
JS TS

import { Controller, Get, Injectable, Module } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

@Injectable()
class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

@Controller()
class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

@Module({
  controllers: [AppController],
  providers: [AppService],
})
class AppModule {}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

It stays the same application as it grows. nest g resource users adds a module, and when src/main.ts gets long, you move the root module to its own file. See One-file projects.

Fastify, Express and Hono start with fewer lines, which stays an advantage while the application stays small. As it grows, where authentication lives, how its parts share services, and how tests replace dependencies become yours to design. Nest decides those up front.

Performance#

Requests per second on GET /, with each server answering Hello world!, using wrk -t4 -c100 for 8 seconds after a 3-second warm-up, on Node.js 24.20. Each number is the median of 3 rounds:

ServerGET /
Fastify57.1k
Nest on Node55.6k
Hono on Node53.1k
Nest on Fastify52.6k
Express37.0k
Nest on Express37.0k

So with the NodeAdapter, Nest handled about 3% fewer requests per second than plain Fastify, about 5% more than Hono on Node, and about 50% more than plain Express.

A separate round on an M1 Pro, with a route parameter and a JSON body:

ServerGET /users/:idPOST /echo (JSON)
Express35.6k30.1k
Fastify54.0k43.5k
Nest on Express34.5k28.2k
Nest on Fastify48.4k40.2k
Nest on Node51.0k42.4k

Here Nest on Node came within 3-6% of plain Fastify and stayed 41-43% ahead of plain Express.

To run these yourself, follow the steps in nestjs/nest#18063, which also have the servers for Hono, Koa, Elysia and AdonisJS. Other machines give different gaps, and Hono's number covers only Node, not the other runtimes it runs on.

These routes do almost no work. A route that queries a database or calls other services spends most of its time waiting for them, so for most applications these differences won't decide the choice.

Moving to Nest later#

The choice doesn't have to be final:

  • From Fastify, Nest runs on the same server through the FastifyAdapter, and your plugins keep working. See Migrating from Fastify.
  • From Express, routes move one at a time with both serving requests, or all at once. See Migrating from Express.
Edit on GitHub

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor