pnpm으로 Monorepo에 Fastify 설정하기
I’m ready to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you already provided) here? Once I have it, I’ll translate it into Korean while preserving the original formatting, markdown, and code blocks.
필수 조건
- Node.js (v22 이상)
- pnpm이 설치됨
Monorepo 구조
최종 구조는 다음과 같습니다:
app-monorepo/
├── package.json
├── pnpm-workspace.yaml
├── apps/
│ └── api/
│ ├── package.json
│ ├── tsconfig.json
│ └── src/
│ ├── app.ts
│ ├── server.ts
│ ├── routes/
│ │ ├── root.ts
│ │ └── users/
│ │ └── index.ts
│ └── plugins/
│ ├── cors.ts
│ ├── helmet.ts
│ └── sensible.ts
└── tsconfig.json
Note:
app.ts를server.ts와 분리하는 것은 공식 Fastify 관례입니다. 이를 통해 HTTP 서버를 시작하지 않고도 앱을 테스트할 수 있습니다.
모노레포 초기화
mkdir app-monorepo
cd app-monorepo
pnpm init
pnpm 워크스페이스 구성
루트에 pnpm-workspace.yaml 파일을 생성합니다:
packages:
- 'apps/*'
TypeScript 구성 (루트)
루트에 tsconfig.json 파일을 생성합니다:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true
},
"exclude": ["**/node_modules", "**/dist"]
}
API 패키지 만들기
mkdir -p apps/api/src/{routes/users,plugins}
cd apps/api
pnpm init
apps/api/package.json:
{
"name": "@monorepo/api",
"version": "1.0.0",
"type": "commonjs",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"test": "node --test",
"type-check": "tsc --noEmit"
},
"dependencies": {
"fastify": "^5.7.4",
"@fastify/autoload": "^6.3.1",
"@fastify/cors": "^11.2.0",
"@fastify/helmet": "^13.0.2",
"@fastify/sensible": "^6.0.4",
"close-with-grace": "^2.4.0",
"fastify-plugin": "^5.1.0"
},
"devDependencies": {
"@types/node": "^25.2.3",
"pino-pretty": "^13.1.3",
"tsx": "^4.21.0",
"typescript": "~5.9.3"
}
}
이제 의존성을 설치합니다:
pnpm install
API에 대한 TypeScript 구성
apps/api/tsconfig.json:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"tsBuildInfoFile": "./dist/.tsbuildinfo",
"composite": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
앱 팩토리 만들기
Fastify 공식 컨벤션은 플러그인과 라우트를 등록하는 팩토리인 app.ts와 서버를 시작하는 진입점인 server.ts를 분리하는 것입니다.
apps/api/src/app.ts
import path from 'path';
import AutoLoad from '@fastify/autoload';
import { FastifyInstance, FastifyPluginOptions, FastifyServerOptions } from 'fastify';
// Fastify server options
export const options: FastifyServerOptions = {
logger: {
level: process.env.LOG_LEVEL || 'debug',
transport:
process.env.LOG_LEVEL === 'debug'
? {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
colorize: true
}
}
: undefined
}
};
export default async function app(
fastify: FastifyInstance,
opts: FastifyPluginOptions
) {
// Automatically load all plugins from the plugins/ folder
await fastify.register(AutoLoad, {
dir: path.join(__dirname, 'plugins'),
options: { ...opts }
});
// Automatically load all routes from the routes/ folder
await fastify.register(AutoLoad, {
dir: path.join(__dirname, 'routes'),
options: { ...opts }
});
}
서버 진입점 만들기
close-with-grace는 Fastify 팀이 권장하는 우아한 종료 패키지입니다. SIGINT, SIGTERM, uncaughtException을 한 곳에서 처리하며, 설정 가능한 delay를 통해 서버가 닫히기 전에 진행 중인 요청이 완료될 시간을 제공합니다.
먼저 설치합니다:
pnpm --filter @monorepo/api add close-with-grace
apps/api/src/server.ts
import Fastify from 'fastify';
import closeWithGrace from 'close-with-grace';
import app, { options } from './app';
// Load .env file (optional)
try {
process.loadEnvFile();
} catch {
// .env is optional
}
// Instantiate Fastify with options exported from app.ts (logger included)
const server = Fastify(options);
// Register the app as a plugin
server.register(app);
// Graceful shutdown configuration
const closeListeners = closeWithGrace(
{ delay: parseInt(process.env.FASTIFY_CLOSE_GRACE_DELAY ?? '500') || 500 },
async function ({ err }) {
if (err) {
server.log.error(err);
}
await server.close();
}
);
server.addHook('onClose', async () => {
closeListeners.uninstall();
});
// Start listening
const PORT = parseInt(process.env.FASTIFY_PORT ?? '3000') || 3000;
server.listen({ port: PORT, host: '0.0.0.0' }, (err) => {
if (err) {
server.log.error({ err }, 'Server shutdown due to an error');
process.exit(1);
}
});
이제 pnpm 워크스페이스 모노레포 안에 완전히 구성된 Fastify 프로젝트가 준비되었습니다. pnpm --filter @monorepo/api dev로 개발 서버를 시작하고, pnpm --filter @monorepo/api build로 빌드하며, pnpm --filter @monorepo/api start로 빌드된 출력을 실행하세요. 즐거운 코딩 되세요!
모든 라우트 자동 로드
await fastify.register(AutoLoad, {
dir: path.join(__dirname, 'routes'),
options: { ...opts }
});
Note:
pino‑pretty는 프로덕션 환경이 아닌 경우에만 활성화됩니다. 프로덕션에서는 원시 JSON 로그가 더 효율적이며 로그 집계 도구에서 더 잘 지원됩니다.
플러그인 만들기
플러그인은 **fastify-plugin**을 사용하여 데코레이션을 전역 컨텍스트에 노출하는 관례를 따릅니다.
apps/api/src/plugins/cors.ts
import fp from 'fastify-plugin';
import { FastifyInstance } from 'fastify';
import Cors from '@fastify/cors';
async function corsPlugin(fastify: FastifyInstance) {
await fastify.register(Cors, {
origin: true,
methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'],
credentials: true
});
}
export default fp(corsPlugin, {
name: 'cors'
});
apps/api/src/plugins/helmet.ts
import fp from 'fastify-plugin';
import { FastifyInstance } from 'fastify';
import helmet from '@fastify/helmet';
async function helmetPlugin(fastify: FastifyInstance) {
await fastify.register(helmet, {
crossOriginResourcePolicy: { policy: 'same-origin' },
crossOriginEmbedderPolicy: true
// Other Helmet options
});
}
export default fp(helmetPlugin, {
name: 'helmet'
});
apps/api/src/plugins/sensible.ts
import fp from 'fastify-plugin';
import sensible from '@fastify/sensible';
export default fp(async (fastify) => {
await fastify.register(sensible);
});
왜
fastify-plugin을 사용하나요?
사용하지 않으면 각 플러그인이 자체 캡슐화된 스코프를 갖게 됩니다.fastify-plugin을 사용하면 데코레이션과 훅이 부모 컨텍스트에 노출되어 전역에서 사용할 수 있게 됩니다.
라우트 생성
routes/ 폴더에 있는 각 파일은 **@fastify/autoload**에 의해 자동으로 로드됩니다. 폴더 구조는 라우트 경로와 동일하게 매핑됩니다.
apps/api/src/routes/root.ts
import { FastifyInstance } from 'fastify';
export default async function (fastify: FastifyInstance) {
fastify.get('/', async (request, reply) => {
return { message: 'Hello from Fastify in monorepo!' };
});
fastify.get('/health', async (request, reply) => {
return { status: 'ok', timestamp: new Date().toISOString() };
});
}
apps/api/src/routes/users/index.ts
import { FastifyInstance } from 'fastify';
export default async function (fastify: FastifyInstance) {
fastify.get('/', async (request, reply) => {
return [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];
});
fastify.get('/:id', async (request, reply) => {
const { id } = request.params as { id: string };
if (isNaN(parseInt(id))) {
return reply.badRequest('ID must be a number');
}
return { id: parseInt(id), name: 'User ' + id };
});
}
Note:
@fastify/autoload는routes/users/index.ts를 자동으로/users경로에 매핑합니다.app.ts에서 라우트를 수동으로 등록할 필요가 없습니다.
루트 스크립트
package.json at the repository root:
{
"scripts": {
"dev": "pnpm --filter @monorepo/api dev",
"build": "pnpm -r build",
"start": "pnpm --filter @monorepo/api start",
"test": "pnpm --filter @monorepo/api test",
"type-check": "pnpm -r type-check"
}
}
유용한 명령어 예시
# Development
pnpm dev
# Build all packages
pnpm build
# Production
pnpm start
# Test
pnpm test
# Add a dependency to the API
pnpm --filter @monorepo/api add fastify-plugin
# Add a dev dependency
pnpm --filter @monorepo/api add -D @types/node
이 가이드가 Fastify 모노레포를 설정하는 데 도움이 되길 바랍니다. 이 게시물은 개선 사항이 추가될 때마다 지속적으로 업데이트될 예정입니다.