High-Performance Node.js REST APIs: Why Fastify is 2x Faster than Express + Zod Validation

· 6 min read

For over a decade,Express.jswas the default web framework for Node.js. However, as modern web apps and multiplayer backends demand tens of thousands of requests per second with sub-millisecond latency, engineers increasingly turn toFastify— an ultra-fast, low-overhead web framework.

1. Performance Anatomy: Radix Tree vs Linear Routing

The core difference lies in how incoming URLs are matched against registered route handlers:

2. Precompiled JSON Serialization:fast-json-stringify

In standard Node.js servers, callingres.json(data)invokes V8's dynamicJSON.stringify()at runtime. Fastify compiles JSON schemas ahead of time usingfast-json-stringify, accelerating JSON serialization by2-3x.

// Fastify automatycznie kompiluje zoptymalizowaną funkcję serializującą
const responseSchema = {
  200: {
    type: 'object',
    properties: {
      status: { type: 'string' },
      userId: { type: 'integer' },
      timestamp: { type: 'number' }
    }
  }
};

3. Strict TypeScript Input Validation with Zod

Validating payloads at the API gateway prevents injection attacks, rejects malformed requests early, and guarantees end-to-end TypeScript type safety:

import Fastify from 'fastify';
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from 'fastify-type-provider-zod';
import { z } from 'zod';

const app = Fastify({ logger: true }).withTypeProvider<ZodTypeProvider>();

// Konfiguracja kompilatorów Zod
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);

// Definicja schematu rejestracji gracza
const RegisterSchema = z.object({
  username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
  email: z.string().email(),
  coins: z.number().int().nonnegative().default(0)
});

// Rejestracja trasy z automatyczną walidacją
app.post('/api/players/register', {
  schema: {
    body: RegisterSchema,
    response: {
      201: z.object({
        success: z.boolean(),
        playerId: z.string(),
        createdAt: z.string()
      })
    }
  }
}, async (request, reply) => {
  // request.body ma w pełni wydedukowany typ TypeScript!
  const { username, email, coins } = request.body;

  // Symulacja zapisu w bazie danych
  const playerId = 'usr_' + Math.random().toString(36).substring(2, 9);

  return reply.status(201).send({
    success: true,
    playerId,
    createdAt: new Date().toISOString()
  });
});

const start = async () => {
  try {
    await app.listen({ port: 3000, host: '0.0.0.0' });
    console.log('🚀 Fastify API serwer działa na porcie 3000');
  } catch (err) {
    app.log.error(err);
    process.exit(1);
  }
};
start();

4. Load Testing Benchmarks (autocannon)

In benchmarks executed withautocannon(100 concurrent connections, 10 seconds sustained load on an 8-core server):

Conclusion:Fastify handles over4x more requests per secondat nearly 4x lower latency and half the memory footprint — making it the clear choice for high-performance game backends and REST APIs.

Also read