들어가며
최근 스프링부트로 구현된 REST API 프로젝트를 node.js, fastify 환경으로 마이그레이션 하고, 클로드 코드 및 코파일럿 등의 에이전트에 PlayWright MCP서버를 연동하여 E2E 테스트를 자동화하는 과제를 진행하게 되었다.
과제 진행을 하면서, 새롭게 학습하고 그 과정 속에서 힘들었던 부분 등을 기록하고자 한다.
이번 글에서는 본격적인 마이그레이션 작업에 앞서, Node.js + TypeScript + Fastify 기반의 프로젝트 환경을 세팅한 과정을 정리해보려고 한다.
목표
기존 Spring Boot 프로젝트는 전형적인 레이어드 아키텍처로 구성되어 있다.
Controller -> Service -> Repository -> Entity (JPA)
이 구조를 최대한 유지하면서 node 환경으로 옮기는 게 우선적인 목표다. 다만 스프링의 의존성 주입이나 자동 설정 같은 편의 기능이 없으니 그 부분을 어떻게 해결할지 고민이 많았다.
프로젝트 초기화
Node.js 프로젝트 초기화
npm 프로젝트를 초기화했다.
npm init -y
-y 옵션을 주면 기본값으로 자동 생성된다. 나중에 package.json에서 수정하면 되니까 일단 빠르게 진행했다.
생성된 package.json을 열어보면 이런 내용이 들어있다.
{
"name": "ack-migration-to-fastify",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
기본 템플릿이지만 일단은 이대로 두고 진행했다.
TypeScript 설정
TypeScript 패키지 설치
npm install -D typescript @types/node tsx
세 가지 패키지를 설치했다
typescript: TypeScript 컴파일러@types/node: Node.js API의 타입 정의tsx: TypeScript를 직접 실행할 수 있는 도구 (개발 시 유용)
tsconfig.json 생성
그 후 타입스크립트 설정 파일을 생성한다.
npx tsc --init
자동 생성된 tsconfig.json에는 많은 옵션들이 주석 처리되어 있다. 나는 필요한 부분만 남기고 다음과 같이 정리했다.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src",
"resolveJsonModule": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
몇 가지 중요한 옵션을 설명하자면
target: "ES2022": 최신 JavaScript 문법 사용strict: true: 엄격한 타입 체크 활성화 (TypeScript의 핵심)outDir: "./dist": 컴파일된 JS 파일이 저장될 위치rootDir: "./src": 소스 코드 위치experimentalDecorators: 데코레이터 사용
Fastify 설치 및 설정
이제 본격적으로 Fastify를 설치하고 간단한 서버를 띄워볼 차례다.
Fastify 설치
npm install fastify
Fastify는 타입스크립트 작성되어 있어서 별도로 @types/fastify 같은 패키지를 설치할 필요가 없다.
환경변수 관리 설정
서버 포트나 데이터베이스 연결 정보 같은 환경변수값은 dotenv 패키지를 사용하기로 했다.
npm install dotenv
npm install -D @types/dotenv
간단한 Fastify 서버 구현
이제 실제로 동작하는 서버를 만들어보자. src/app.ts 파일을 생성했다.
import Fastify from 'fastify';
const app = Fastify({
logger: true
});
app.get('/health', async (request, reply) => {
return { status: 'ok' };
});
export default app;
Fastify 인스턴스를 생성하고 간단한 헬스체크 엔드포인트를 만들었다. logger: true 옵션을 주면 Fastify의 내장 로거(Pino)가 활성화된다.
다음으로 src/server.ts 파일을 만들어서 서버를 실제로 시작하는 코드를 작성했다.
import 'dotenv/config';
import app from './app';
const PORT = Number(process.env.PORT) || 3000;
const start = async () => {
try {
await app.listen({ port: PORT, host: '0.0.0.0' });
console.log(`서버 실행: http://localhost:${PORT}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
맨 위에 import 'dotenv/config'를 넣어서 .env 파일을 자동으로 로드하도록 했다.
package.json 스크립트 추가
이제 서버를 쉽게 실행할 수 있도록 npm 스크립트를 추가했다. package.json 을 열어서 scripts 부분을 수정했다.
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"type-check": "tsc --noEmit"
}
각 스크립트의 역할은 다음과같다.
dev: 개발 모드로 실행 (파일 변경 시 자동 재시작)build: TypeScript를 JavaScript로 컴파일start: 빌드된 파일을 실행 (프로덕션 환경)type-check: 타입 체크만 수행 (빌드는 안 함)
서버 실행 테스트
드디어 서버를 실행해볼 시간이다.
npm run dev
터미널에 이런 로그가 출력되면 성공이다.
서버 실행: http://localhost:3000
{"level":30,"time":1699999999999,"pid":12345,"hostname":"localhost","msg":"Server listening at http://0.0.0.0:3000"}
{"status":"ok"}
curl 명령어로 헬스체크 엔드포인트를 호출한 결과 응답이 잘 오는 것을 확인할 수 있었다.
핫 리로딩 확인
app.get('/ping', async (request, reply) => {
return { message: 'pong' };
});
tsx watch 덕분에 코드를 수정하면 자동으로 서버가 재시작된다. 이게 정말 편하다.src/app.ts에 새로운 라우트를 추가해봤다.
curl http://localhost:3000/ping
{"message":"pong"}
파일을 저장하자마자 터미널에서 서버가 재시작이 되고, 다시 curl로 테스트해보니 pong이 잘 응답되는 것을 확인할 수 있었다.
마치며
기본적인 node + ts + fastify 환경 설정을 완료했다. 스프링 부트의 자동 설정에 비하면 손이 좀 더 가지만, 그만큼 제어할 수 있는 범위가 넓다는 장점이 있는게 아닐까 생각든다.
다음 단계로는 데이터베이스 연동 작업을 할 예정이다.