Wydajne REST API w Node.js: Dlaczego Fastify jest 2x szybszy od Expressa + walidacja Zod

· 6 min czytania

Przez ponad dekadę framework Express.js był bezdyskusyjnym liderem środowiska Node.js. Jednak w miarę jak współczesne aplikacje internetowe i gry wieloosobowe wymagają obsługi dziesiątek tysięcy żądań na sekundę przy minimalnych opóźnieniach, architektura Expressa zaczyna ujawniać swoje ograniczenia. Odpowiedzią inżynierów na te wyzwania jest Fastify — ultralekki framework stawiający na bezkompromisową wydajność.

1. Anatomia wydajności: Radix Tree vs Liniowy Router Expressa

Główna różnica tkwi w sposobie, w jaki framework dopasowuje przychodzący adres URL do zarejestrowanego handlera trasy:

2. Przełomowa serializacja JSON: fast-json-stringify

W tradycyjnym serwerze Node.js wywołanie res.json(data) używa wbudowanej funkcji JSON.stringify() silnika V8, która musi dynamicznie badać typ każdej właściwości obiektu w trakcie działania. Fastify wprowadza prekompilowane schematy serializacji za pomocą fast-json-stringify, co przyspiesza przekształcanie obiektów w ciąg znaków nawet 2-3x.

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

3. Ścisła walidacja danych wejściowych z biblioteką Zod

Walidacja danych na poziomie bramki API zapobiega atakom typu Injection, odrzuca niekompletne payloady zanim dotrą do logiki biznesowej i gwarantuje 100% bezpieczeństwo typów (Type Safety) w TypeScript:

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. Wyniki testów obciążeniowych (Benchmark)

W testach przeprowadzonych narzędziem autocannon (100 współbieżnych połączeń, 10 sekund ciągłego obciążenia na maszynie 8-rdzeniowej):

Wniosek: Fastify przetwarza ponad 4-krotnie więcej zapytań na sekundę przy niemal 4-krotnie niższych opóźnieniach, zużywając przy tym o połowę mniej pamięci RAM. Jeśli budujesz nowoczesne API dla gier, aplikacji mobilnych lub systemów IoT — Fastify jest bezdyskusyjnym zwycięzcą.

Zobacz także