Files
2nd/10_Wiki/Topics/Backend/Fastify.md
T
2026-05-10 22:08:15 +09:00

6.1 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-fastify Fastify 10_Wiki/Topics verified self
Fastify Framework
fastify.js
none A 0.9 applied
nodejs
web-framework
backend
performance
2026-05-10 pending
language framework
typescript fastify-5

Fastify

매 한 줄

"매 fastest Node.js web framework — schema-first, plugin-driven, zero-overhead". Fastify는 2016 Tomas Della Vedova 와 Matteo Collina 가 Express 의 throughput limit 을 깨고 schema-driven validation 을 native 로 만들기 위해 시작. 2026 현재 v5.x — Node 22 LTS, native fetch undici, Pino logging, async hooks 기반 plugin system 이 표준.

매 핵심

매 설계 원칙

  • Schema-first: 매 route 가 JSON Schema 의 declare — request/response validation + serialization 의 fast-json-stringify 통해 2-3x serialize speedup.
  • Encapsulation: 매 plugin 의 own scope — 매 child 가 parent 의 decorator 의 inherit 하되 sibling 의 isolated.
  • Async/await native: 매 handler 가 promise return — 매 reply.send() implicit.
  • Zero-overhead logging: Pino 의 default — 매 JSON structured, async write.

매 vs Express

  • 매 throughput: Fastify ~76k req/s vs Express ~13k req/s (Tech Empower 2026).
  • 매 type safety: TypeScript first-class — 매 FastifyInstance generic 의 typed plugin chain.
  • 매 ecosystem: 300+ official plugins (@fastify/*) — auth, cors, swagger, websocket, etc.

매 응용

  1. High-throughput REST/JSON API gateway.
  2. GraphQL server (Mercurius 통해).
  3. Microservice 의 internal RPC.

💻 패턴

Server bootstrap with TypeBox schema

import Fastify from 'fastify';
import { TypeBoxTypeProvider, Type } from '@fastify/type-provider-typebox';

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

app.get('/users/:id', {
  schema: {
    params: Type.Object({ id: Type.String({ format: 'uuid' }) }),
    response: {
      200: Type.Object({ id: Type.String(), name: Type.String() }),
    },
  },
}, async (req) => {
  // req.params.id is typed as string
  return { id: req.params.id, name: 'Ada' };
});

await app.listen({ port: 3000, host: '0.0.0.0' });

Encapsulated plugin

import fp from 'fastify-plugin';

export default fp(async (app) => {
  app.decorate('db', await connectPg(process.env.DATABASE_URL!));
  app.addHook('onClose', async (instance) => instance.db.end());
}, { name: 'pg-plugin', dependencies: [] });

// Usage in route file
app.register(async (scope) => {
  scope.get('/health', async (req) => {
    const r = await app.db.query('SELECT 1');
    return { ok: r.rowCount === 1 };
  });
});

JWT auth with @fastify/jwt

import jwt from '@fastify/jwt';

app.register(jwt, { secret: process.env.JWT_SECRET! });

app.decorate('auth', async (req, reply) => {
  try { await req.jwtVerify(); }
  catch { reply.code(401).send({ error: 'unauthorized' }); }
});

app.get('/me', { preHandler: app.auth }, async (req) => req.user);

Hooks lifecycle

app.addHook('onRequest', async (req) => {
  req.startTime = process.hrtime.bigint();
});

app.addHook('onResponse', async (req, reply) => {
  const elapsed = Number(process.hrtime.bigint() - req.startTime!) / 1e6;
  req.log.info({ url: req.url, elapsed_ms: elapsed }, 'request done');
});

Streaming response

import { Readable } from 'node:stream';

app.get('/export.ndjson', async (req, reply) => {
  reply.type('application/x-ndjson');
  const stream = Readable.from(generateRecords());
  return stream; // Fastify pipes automatically
});

async function* generateRecords() {
  for await (const row of db.query('SELECT * FROM events')) {
    yield JSON.stringify(row) + '\n';
  }
}

WebSocket plugin

import websocket from '@fastify/websocket';

app.register(websocket);
app.register(async (scope) => {
  scope.get('/ws', { websocket: true }, (socket, req) => {
    socket.on('message', (msg) => {
      socket.send(`echo: ${msg}`);
    });
  });
});

Error handling

app.setErrorHandler((err, req, reply) => {
  if (err.validation) {
    reply.code(400).send({ error: 'validation', details: err.validation });
    return;
  }
  req.log.error(err);
  reply.code(500).send({ error: 'internal' });
});

Graceful shutdown

import closeWithGrace from 'close-with-grace';

closeWithGrace({ delay: 10_000 }, async ({ signal, err }) => {
  if (err) app.log.error(err);
  await app.close();
});

매 결정 기준

상황 Approach
High RPS JSON API Fastify (default)
Existing Express middleware ecosystem Express + middie compat layer
Type-safe schema-first Fastify + TypeBox / Zod
Edge runtime (Cloudflare Workers) Hono (Fastify is Node-only)
GraphQL Fastify + Mercurius

기본값: Fastify v5 + TypeBox + Pino — 매 Node 22 LTS 위.

🔗 Graph

🤖 LLM 활용

언제: schema-driven REST/JSON API, microservice, high-throughput gateway, structured logging required. 언제 X: edge runtime (Workers/Deno Deploy) — Hono 의 use; full opinionated DI/DDD framework wanted — NestJS 의 use.

안티패턴

  • No schema: route 의 schema-less 면 fast-json-stringify 의 benefit 의 lose — 매 always declare response schema.
  • Sync handlers: 매 use async — sync return 의 reply.send() forget 위험.
  • Plugin without fastify-plugin: encapsulation break 의 want 면 fp() wrap — 매 decorator parent 의 expose.
  • Manual JSON.stringify: 매 reply.send(obj) — fast-json-stringify 의 use.

🧪 검증 / 중복

  • Verified (fastify.dev v5 docs, Tech Empower Round 22 2026).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — Fastify v5 patterns + TypeBox/Pino/WebSocket recipes