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

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

YS
김영삼
조회 656

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로 등록