들어가며

최근 스프링부트로 구현된 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 환경 설정을 완료했다. 스프링 부트의 자동 설정에 비하면 손이 좀 더 가지만, 그만큼 제어할 수 있는 범위가 넓다는 장점이 있는게 아닐까 생각든다.
다음 단계로는 데이터베이스 연동 작업을 할 예정이다.