Cursor Agent Mode 실전 사례: 핀테크 스타트업의 Express.js → NestJS 3일 마이그레이션
핀테크 스타트업의 레거시 API 리팩토링: Cursor Agent Mode 실전 사례
결제 처리 API 32개 엔드포인트를 운영하는 핀테크 스타트업 ‘페이브릿지(가명)‘는 Express.js 기반 레거시 코드의 유지보수 한계에 직면했습니다. 타입 안전성 부재, 테스트 커버리지 12%, 모놀리식 라우터 구조가 문제였습니다. Cursor Agent Mode를 활용해 NestJS로 3일 만에 전환한 실제 워크플로우를 공유합니다.
1일차: 프로젝트 셋업과 Docs 인덱싱 설정
Step 1. NestJS 프로젝트 초기화
npm i -g @nestjs/cli
nest new paybridge-api —strict
cd paybridge-api
npm install @nestjs/swagger @nestjs/config class-validator class-transformer
npm install @nestjs/typeorm typeorm pg
Step 2. Cursor에서 Docs 인덱싱 활성화
Cursor Settings → Features → Docs에서 프로젝트에 필요한 문서를 인덱싱합니다.
// .cursor/settings.json
{
"docs": [
{
"name": "NestJS",
"url": "https://docs.nestjs.com"
},
{
"name": "TypeORM",
"url": "https://typeorm.io"
},
{
"name": "class-validator",
"url": "https://github.com/typestack/class-validator#readme"
}
]
}
Docs 인덱싱이 완료되면 Agent Mode가 NestJS 공식 문서의 최신 패턴을 참조하여 코드를 생성합니다. Cmd+L (Mac) 또는 Ctrl+L (Windows)로 채팅을 열고 Agent Mode를 선택합니다.
Step 3. .cursorrules 파일로 코드베이스 컨텍스트 설정
// .cursorrules
You are working on a fintech NestJS API migration.
- Use strict TypeScript with no
anytypes - All endpoints must have Swagger decorators
- Use class-validator for DTO validation
- Follow NestJS modular architecture: module/controller/service/dto
- Database: PostgreSQL with TypeORM
- Auth: JWT Bearer tokens
- Error responses follow RFC 7807 Problem Details format
All amounts are in integer (cents), never floating point
2일차: Agent Mode로 멀티파일 마이그레이션 실행
Step 4. 레거시 라우터를 NestJS 모듈로 변환
Agent Mode(Ctrl+I)를 열고 다음 프롬프트를 입력합니다:
@workspace 레거시 Express 라우터 /routes/payments.js를 분석하고
NestJS 모듈 구조로 변환해줘.
payments.module.ts, payments.controller.ts, payments.service.ts,
create-payment.dto.ts, payment.entity.ts 파일을 한 번에 생성해줘.
기존 비즈니스 로직은 보존하면서 타입 안전성을 추가해.
Agent Mode가 생성하는 멀티파일 결과 예시:
// src/payments/dto/create-payment.dto.ts
import { IsString, IsInt, Min, IsEnum } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export enum PaymentMethod {
CARD = ‘CARD’,
BANK_TRANSFER = ‘BANK_TRANSFER’,
VIRTUAL_ACCOUNT = ‘VIRTUAL_ACCOUNT’,
}
export class CreatePaymentDto {
@ApiProperty({ description: ‘결제 금액 (원 단위 정수)’, example: 50000 })
@IsInt()
@Min(100)
amount: number;
@ApiProperty({ enum: PaymentMethod })
@IsEnum(PaymentMethod)
method: PaymentMethod;
@ApiProperty({ description: ‘주문 ID’ })
@IsString()
orderId: string;
}
// src/payments/payments.controller.ts import { Controller, Post, Body, UseGuards, HttpCode } from ‘@nestjs/common’; import { ApiBearerAuth, ApiOperation, ApiResponse } from ‘@nestjs/swagger’; import { JwtAuthGuard } from ’../auth/jwt-auth.guard’; import { PaymentsService } from ’./payments.service’; import { CreatePaymentDto } from ’./dto/create-payment.dto’;@Controller(‘api/v2/payments’) @ApiBearerAuth() @UseGuards(JwtAuthGuard) export class PaymentsController { constructor(private readonly paymentsService: PaymentsService) {}
@Post() @HttpCode(201) @ApiOperation({ summary: ‘결제 생성’ }) @ApiResponse({ status: 201, description: ‘결제가 성공적으로 생성됨’ }) async createPayment(@Body() dto: CreatePaymentDto) { return this.paymentsService.create(dto); } }
// src/payments/payments.service.ts import { Injectable, BadRequestException } from ‘@nestjs/common’; import { InjectRepository } from ‘@nestjs/typeorm’; import { Repository } from ‘typeorm’; import { Payment } from ’./entities/payment.entity’; import { CreatePaymentDto } from ’./dto/create-payment.dto’;@Injectable() export class PaymentsService { constructor( @InjectRepository(Payment) private readonly paymentRepo: Repository
, ) {}
async create(dto: CreatePaymentDto): Promise{ const payment = this.paymentRepo.create({ …dto, status: ‘PENDING’, pgTransactionId: null, }); return this.paymentRepo.save(payment); } }
Agent Mode는 @workspace 컨텍스트를 통해 기존 Express 라우터의 미들웨어 체인, 에러 핸들링 패턴, DB 쿼리를 분석한 뒤 NestJS 관용 패턴으로 변환합니다. 한 번의 프롬프트로 5개 파일이 동시에 생성되며, Accept All 버튼으로 일괄 적용합니다.
3일차: 자동 테스트 생성과 검증
Step 5. Agent Mode로 E2E 테스트 자동 생성
@payments.controller.ts @payments.service.ts
이 모듈에 대한 단위 테스트와 E2E 테스트를 생성해줘.
Jest + supertest 사용, 성공/실패/엣지 케이스 모두 포함.
생성된 테스트 코드:
// test/payments.e2e-spec.ts
import { Test, TestingModule } from ‘@nestjs/testing’;
import { INestApplication, ValidationPipe } from ‘@nestjs/common’;
import * as request from ‘supertest’;
import { AppModule } from ’../src/app.module’;
describe(‘PaymentsController (e2e)’, () => {
let app: INestApplication;
const validToken = ‘Bearer YOUR_TEST_JWT_TOKEN’;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();
});
it(‘POST /api/v2/payments - 정상 결제 생성’, () => {
return request(app.getHttpServer())
.post(‘/api/v2/payments’)
.set(‘Authorization’, validToken)
.send({ amount: 50000, method: ‘CARD’, orderId: ‘ORD-001’ })
.expect(201)
.expect((res) => {
expect(res.body.status).toBe(‘PENDING’);
});
});
it(‘POST /api/v2/payments - 금액 음수 시 400’, () => {
return request(app.getHttpServer())
.post(‘/api/v2/payments’)
.set(‘Authorization’, validToken)
.send({ amount: -100, method: ‘CARD’, orderId: ‘ORD-002’ })
.expect(400);
});
afterAll(async () => {
await app.close();
});
});
Step 6. 테스트 실행 및 커버리지 확인
npx jest --coverage --verbose
# 또는 Agent Mode 터미널에서 직접 실행
npm run test:e2e
마이그레이션 결과 요약
| 지표 | Before (Express.js) | After (NestJS) |
|---|---|---|
| 테스트 커버리지 | 12% | 78% |
| 타입 안전성 | 없음 (JS) | Strict TypeScript |
| API 문서화 | 수동 Postman | Swagger 자동 생성 |
| DTO 유효성 검사 | 수동 if문 | class-validator 데코레이터 |
| 소요 기간 | - | 3일 (Agent Mode 활용) |
| 변환된 엔드포인트 | 32개 | 32개 |
@NestJS로 직접 문서를 참조할 수 있습니다.- **단계적 프롬프트 전략**: 32개 엔드포인트를 한 번에 변환하지 말고, 도메인별(payments, users, orders)로 나누어 프롬프트를 작성하면 컨텍스트 윈도우를 효율적으로 사용합니다.- **.cursorrules 파일 활용**: 프로젝트 루트에 코딩 규칙을 명시하면 모든 Agent 응답에 자동 적용됩니다.- **Composer Mode로 대규모 편집**: 10개 이상의 파일을 동시에 수정할 때는 Ctrl+Shift+I로 Composer를 사용하세요.
## Troubleshooting: 자주 발생하는 문제
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| Agent가 Express 문법으로 코드 생성 | Docs 인덱싱 미완료 또는 .cursorrules 미설정 | Settings → Docs에서 NestJS 인덱싱 상태 확인, .cursorrules에 NestJS 명시 |
| 멀티파일 생성 시 import 경로 오류 | 프로젝트 구조를 Agent가 잘못 인식 | @workspace 태그를 포함하여 전체 구조를 컨텍스트에 추가 |
| 테스트 생성 시 모듈 의존성 누락 | Agent가 module.ts의 providers를 참조하지 못함 | 관련 module.ts 파일을 @payments.module.ts로 명시적 참조 |
| Agent Mode 응답이 느리거나 끊김 | 컨텍스트 윈도우 초과 | 대화를 리셋(Ctrl+L → New Chat)하고 필요한 파일만 참조 |
| TypeORM 엔티티 데코레이터 누락 | tsconfig의 experimentalDecorators 미설정 | tsconfig.json에 "experimentalDecorators": true, "emitDecoratorMetadata": true 추가 |
Q1. Cursor Agent Mode로 마이그레이션할 때 기존 비즈니스 로직이 손실될 수 있나요?
Agent Mode는 @workspace 컨텍스트로 기존 코드를 분석한 뒤 변환하므로 로직 보존율이 높습니다. 다만 복잡한 미들웨어 체인이나 동적 라우팅은 수동 검증이 필요합니다. 변환 전에 기존 API의 통합 테스트를 먼저 작성하여 동작을 검증한 뒤 마이그레이션하는 것을 권장합니다.
Q2. Docs 인덱싱과 일반 채팅의 차이는 무엇인가요?
일반 채팅은 모델의 학습 데이터(특정 시점까지)에 의존하지만, Docs 인덱싱은 지정한 공식 문서의 최신 내용을 실시간으로 참조합니다. NestJS v10에서 v11로 업데이트된 API 변경사항도 인덱싱된 문서를 통해 정확하게 반영됩니다. @Docs 태그로 특정 문서를 프롬프트에 직접 참조할 수 있습니다.
Q3. 32개 엔드포인트 전체를 한 번의 프롬프트로 변환할 수 있나요?
기술적으로 가능하지만 권장하지 않습니다. 컨텍스트 윈도우 제한으로 인해 후반부 엔드포인트의 품질이 저하될 수 있습니다. 도메인 단위(결제, 사용자, 주문 등)로 나누어 5~8개씩 변환하고, 각 단계에서 테스트를 실행하여 검증하는 점진적 접근이 안정적입니다.