Skip to content
NestJS ns performance 4 min read

Fastify for Performance

NestJS is platform-agnostic at the HTTP layer: by default it runs on top of Express, but it can run on Fastify with almost no changes to your application code. Fastify is built around a low-overhead routing engine and schema-based serialization, which makes it one of the fastest Node.js web frameworks available. For request-heavy APIs, swapping the underlying adapter is often the single highest-leverage performance change you can make.

Why Fastify is faster

Two design decisions drive Fastify’s throughput advantage. First, its router uses a radix tree (find-my-way) that resolves routes in near-constant time regardless of how many routes you register. Second, and more importantly, Fastify compiles JSON Schemas into purpose-built serialization functions with fast-json-stringify. Instead of calling the generic, reflective JSON.stringify on every response, Fastify runs a function that already knows the exact shape of your payload — frequently 2-3x faster on large objects.

Express, by contrast, leans on middleware chains and JSON.stringify, which are flexible but carry more per-request overhead.

Installing the adapter

Install the platform package alongside Fastify itself:

npm install @nestjs/platform-fastify

You can then remove @nestjs/platform-express if you no longer need it, though keeping it installed is harmless.

Switching the bootstrap

The only mandatory change is in your entry file. Pass a FastifyAdapter instance to NestFactory.create and type the application as NestFastifyApplication.

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({ logger: false }),
  );

  // Fastify binds to 127.0.0.1 by default; use '0.0.0.0' for containers.
  await app.listen(3000, '0.0.0.0');
}
bootstrap();

Note the second argument to listen. Express defaults to all interfaces, but Fastify binds to localhost only. Inside Docker or Kubernetes you must pass '0.0.0.0' or the service will be unreachable.

Your controllers, providers, modules, pipes, and guards remain unchanged. NestJS abstracts the request/response objects, so idiomatic code keeps working.

import { Controller, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(id);
  }
}

What must change versus Express

Most differences only surface if you reached for the raw platform objects or Express-specific middleware.

ConcernExpressFastify
Native request/responseExpress.Request / ResponseFastifyRequest / FastifyReply
Middleware ecosystemhelmet, cors, compressionUse @fastify/* plugins where possible
Static assetsServeStaticModule (express)@fastify/static via the adapter
File uploadsmulter@fastify/multipart
Sending a response manuallyres.status(200).send()reply.status(200).send()

If you inject the native response with @Res(), update the type:

import { Controller, Get, Res } from '@nestjs/common';
import { FastifyReply } from 'fastify';

@Controller('health')
export class HealthController {
  @Get()
  check(@Res() reply: FastifyReply) {
    reply.status(200).send({ status: 'ok' });
  }
}

Register Fastify plugins through the adapter’s underlying instance:

import helmet from '@fastify/helmet';

const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter(),
);
await app.register(helmet);

Schema-based serialization

To unlock Fastify’s biggest win, attach a JSON Schema to your routes so it can compile a fast serializer. You can declare the schema directly on a route via the adapter, or — more idiomatically in Nest — model responses and let Fastify infer fast paths for primitives. A focused, hand-written schema looks like this:

const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter(),
);

const instance = app.getHttpAdapter().getInstance();

instance.addSchema({
  $id: 'user',
  type: 'object',
  properties: {
    id: { type: 'string' },
    email: { type: 'string' },
    createdAt: { type: 'string', format: 'date-time' },
  },
});

Routes that reference { $ref: 'user#' } in their response schema serialize through compiled code instead of JSON.stringify.

Benchmarking the difference

Never trust a performance change you have not measured. Use a load generator such as autocannon against the same endpoint on both adapters.

npx autocannon -c 100 -d 20 http://localhost:3000/users/42

Output:

Running 20s test @ http://localhost:3000/users/42
100 connections

Express adapter
Req/Sec   18,420
Latency   5.31 ms

Fastify adapter
Req/Sec   31,870
Latency   3.02 ms

Numbers vary by hardware, payload size, and middleware, but a 40-70% throughput gain on JSON-heavy endpoints is typical. Run each test several times, discard the warm-up run, and keep the machine otherwise idle so the comparison is fair.

Best practices

  • Always pass '0.0.0.0' to listen when deploying in containers — the Fastify default of localhost is a common silent outage.
  • Prefer @fastify/* plugins over Express middleware; mixing ecosystems forfeits much of the speed advantage.
  • Attach JSON Schemas to high-traffic routes so Fastify can compile dedicated serializers.
  • Disable the Fastify logger ({ logger: false }) in production and route logging through your own structured logger to avoid double work.
  • Benchmark before and after with autocannon, comparing identical endpoints under identical load.
  • Audit @Res() usage and native request typing during the migration — these are the only places application code typically breaks.
Last updated June 14, 2026
Was this helpful?