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:
- Express.js: Stosuje liniową tablicę tras i wyrażenia regularne (
RegExp). Przy każdym zapytaniu Express iteruje po kolei przez zdefiniowane ścieżki i middleware, co przy kilkudziesięciu endpointach generuje zauważalny narzut procesora o złożoności O(n). - Fastify: Wykorzystuje strukturę danych Radix Tree (drzewo przedrostkowe za pośrednictwem biblioteki
find-my-way). Czas wyszukiwania trasy jest deterministyczny i zależy wyłącznie od długości ścieżki URL — złożoność O(k), niezależnie od tego, czy masz 10 czy 1000 tras!
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):
- Express 4.x: ~18 400 req/sec, średni czas odpowiedzi 5.4 ms
- Fastify 4.x: ~74 800 req/sec, średni czas odpowiedzi 1.3 ms