본문 바로가기
Backend2025년 7월 13일7분 읽기

Fastify 플러그인 시스템으로 모듈화된 API 서버

YS
김영삼
조회 681
Fastify 플러그인 시스템으로 모듈화된 API 서버

Fastify 플러그인 아키텍처 이해

Fastify는 Express와 달리 플러그인 기반의 캡슐화(encapsulation)를 핵심 설계 원칙으로 삼고 있습니다. 각 플러그인은 독립적인 컨텍스트를 가지며, 데코레이터와 훅은 해당 스코프 내에서만 유효합니다.

기본 플러그인 구조

// plugins/db.js
const fp = require('fastify-plugin');

async function dbPlugin(fastify, opts) {
  const pool = new Pool({
    host: opts.host || 'localhost',
    port: opts.port || 5432,
    database: opts.database,
  });

  // 데코레이터로 fastify 인스턴스에 db 추가
  fastify.decorate('db', pool);

  // 서버 종료 시 커넥션 정리
  fastify.addHook('onClose', async () => {
    await pool.end();
  });
}

// fp()로 감싸면 캡슐화를 깨고 부모 스코프에 공유
module.exports = fp(dbPlugin, {
  name: 'db-plugin',
  dependencies: [],
});

라우트 플러그인 모듈화

// routes/users/index.js
async function userRoutes(fastify, opts) {
  // 이 스코프 전용 훅
  fastify.addHook('preHandler', async (req, reply) => {
    // 인증 체크
    if (!req.headers.authorization) {
      reply.code(401).send({ error: 'Unauthorized' });
    }
  });

  fastify.get('/', {
    schema: {
      querystring: {
        type: 'object',
        properties: {
          page: { type: 'integer', default: 1 },
          limit: { type: 'integer', default: 20 },
        },
      },
      response: {
        200: {
          type: 'object',
          properties: {
            users: { type: 'array' },
            total: { type: 'integer' },
          },
        },
      },
    },
    handler: async (req, reply) => {
      const { page, limit } = req.query;
      const offset = (page - 1) * limit;
      const result = await fastify.db.query(
        'SELECT id, name, email FROM users LIMIT $1 OFFSET $2',
        [limit, offset]
      );
      return { users: result.rows, total: result.rowCount };
    },
  });

  fastify.post('/', {
    schema: {
      body: {
        type: 'object',
        required: ['name', 'email'],
        properties: {
          name: { type: 'string', minLength: 2 },
          email: { type: 'string', format: 'email' },
        },
      },
    },
    handler: async (req, reply) => {
      const { name, email } = req.body;
      const result = await fastify.db.query(
        'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
        [name, email]
      );
      return reply.code(201).send(result.rows[0]);
    },
  });
}

module.exports = userRoutes;

앱 등록 구조

// app.js
const fastify = require('fastify')({ logger: true });
const autoLoad = require('@fastify/autoload');
const path = require('path');

// 플러그인 자동 로드
fastify.register(autoLoad, {
  dir: path.join(__dirname, 'plugins'),
  options: { database: 'myapp' },
});

// 라우트 자동 로드 (접두사 자동 매핑)
fastify.register(autoLoad, {
  dir: path.join(__dirname, 'routes'),
  options: {},
  routeParams: true,  // 폴더명을 파라미터로 매핑
});

fastify.listen({ port: 3000 }, (err) => {
  if (err) { fastify.log.error(err); process.exit(1); }
});

플러그인 vs Express 미들웨어 비교

특성Fastify 플러그인Express 미들웨어
캡슐화스코프 기반 격리전역 공유
의존성 관리자동 순서 해결수동 순서 지정
스키마 검증내장 JSON Schema별도 라이브러리 필요
성능직렬화 최적화별도 최적화 없음
  • fastify-plugin(fp)으로 감싸면 캡슐화를 해제하여 공유 가능
  • @fastify/autoload로 디렉터리 기반 자동 등록
  • 스키마 기반 직렬화로 JSON.stringify 대비 2-3배 빠름
  • 데코레이터 패턴으로 의존성 주입(DI) 구현

Fastify의 플러그인 시스템은 대규모 API 서버에서 코드 분리와 테스트 용이성을 크게 향상시킵니다. 적절한 캡슐화 전략을 통해 마이크로서비스 수준의 모듈 격리를 모노리스 내에서 구현할 수 있습니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록