Java + Spring Boot 백엔드 개발자가 NestJS로 넘어올 때 바로 감을 잡을 수 있도록 만든 학습용 클론 프로젝트다.
목표는 "쿠팡 스타일 이커머스 백엔드"를 NestJS로 설계하면서도 Spring Boot에서 익숙한 개념과 대응 관계를 자연스럽게 이해하는 것이다.
이 README는 아래 순서대로 설명한다.
- Spring Boot와 NestJS 개념 매핑표
- 프로젝트 폴더 구조 제안
- DB 설계
- 인증/인가 코드
- 상품/장바구니/주문 API 구현
- Docker 및 ECS/CodePipeline 배포 전략
- Swagger 적용
| Spring Boot | NestJS | 설명 |
|---|---|---|
@RestController |
@Controller() |
HTTP 요청 진입점 |
@Service |
@Injectable() service |
비즈니스 로직 계층 |
@Repository |
TypeORM Repository / Prisma Client | DB 접근 계층 |
@Entity |
TypeORM @Entity() / Prisma schema model |
정형 데이터 모델 |
@Configuration |
@Module() + provider 등록 |
의존성 조립과 설정 |
@Bean, DI |
provider + constructor injection | DI 방식은 매우 유사 |
| Spring Security Filter | Guard / Strategy / Interceptor / Middleware | 요청 전후 보안 처리 |
UserDetailsService |
PassportStrategy validate |
JWT payload 해석 후 사용자 주입 |
@PreAuthorize |
@UseGuards() + @Roles() |
메서드/핸들러 권한 제어 |
@Valid |
ValidationPipe + DTO decorator |
요청 검증 |
@ControllerAdvice + @ExceptionHandler |
Exception Filter | 전역 예외 처리 |
| JPA Auditing | BaseEntity + CreateDateColumn, UpdateDateColumn |
생성/수정 시간 관리 |
application.yml |
.env + ConfigModule |
설정 관리 |
NestJS는 Express/Fastify 위에 올라간 프레임워크지만, 개발 경험은 상당히 Spring스럽다.
특히 Module, Injectable, Controller, Guard, Pipe 조합은 Spring의 Configuration, Bean, Controller, Filter, Validator 감각과 많이 닮아 있다.
- DI 중심 구조다.
- 계층형 설계를 자연스럽게 유도한다.
- 애너테이션 기반 선언형 프로그래밍이 가능하다.
- NestJS는 TypeScript 런타임과 Node.js 이벤트 루프 위에서 동작한다.
- JPA처럼 영속성 컨텍스트를 자동으로 믿기보다는, 명시적인 저장과 조회 흐름을 더 자주 의식하게 된다.
- Security도 Servlet Filter Chain보다는 Guard, Strategy, Decorator 조합으로 생각하는 편이 더 자연스럽다.
src
├── common
│ ├── auth
│ │ ├── current-user.decorator.ts
│ │ ├── jwt-auth.guard.ts
│ │ ├── roles.decorator.ts
│ │ └── roles.guard.ts
│ ├── database
│ │ └── base.entity.ts
│ └── enums
│ └── user-role.enum.ts
├── modules
│ ├── auth
│ │ ├── dto
│ │ ├── entities
│ │ ├── presentation
│ │ ├── service
│ │ └── strategy
│ ├── product
│ ├── cart
│ └── order
├── app.module.ts
└── main.ts
Spring Boot에서 controller/service/repository/domain으로 나누는 감각을 유지하되, NestJS에서는 기능 모듈 단위로 먼저 묶는 편이 유지보수에 좋다.
그래서 auth, product, cart, order 같은 bounded context 단위로 분리하고, 공통 관심사는 common에 두었다.
- 도메인별 패키지 분리가 가능하다.
- service 계층과 entity 계층을 분리한다.
- NestJS는
@Module()이 실제 조립 단위다. - 패키지보다 "모듈 import/export 관계"를 더 자주 의식하게 된다.
이 프로젝트는 요구사항에 맞춰 PostgreSQL과 MongoDB를 역할 분리해서 사용한다.
PostgreSQL에는 아래 데이터를 둔다.
usersproductscartscart_itemsordersorder_items
MongoDB에는 아래처럼 변화 이력과 로그를 둔다.
order_logs- 추후 확장:
product_view_logs,payment_event_logs,delivery_tracking_logs
주문, 상품, 장바구니는 조인과 트랜잭션이 중요한 정형 데이터라 PostgreSQL이 적합하다.
반면 주문 생성 로그, 이벤트 이력, 조회 로그는 스키마 변경이 비교적 자유롭고 쓰기 위주라 MongoDB가 잘 맞는다.
- Spring Boot에서도
RDB + Mongo혼합 아키텍처를 자주 쓴다. - 핵심 트랜잭션은 RDB에 두고, 부가 로그는 NoSQL로 분리한다.
- NestJS에서는 같은 앱 안에
TypeOrmModule과MongooseModule을 같이 붙이는 흐름이 비교적 간단하다. - Java에서
JpaRepository,MongoRepository를 나누듯, 여기서는 TypeORM Entity와 Mongoose Schema를 나눈다.
users (1) --- (N) orders
users (1) --- (1) carts
carts (1) --- (N) cart_items
products (1) --- (N) cart_items
orders (1) --- (N) order_items
products (1) --- (N) order_items
- Spring JPA와 가장 비슷하게 느껴진다.
@Entity,Repository, relation decorator가 익숙하다.- "JPA스럽게 연습"하려면 진입 장벽이 낮다.
- 타입 안전성과 schema 기반 개발 경험이 좋다.
- SQL이 어떻게 나갈지 비교적 예측하기 쉽다.
- relation loading과 migration 흐름이 JPA보다는 명시적이다.
- 처음에는 TypeORM이 더 쉽게 들어온다.
- 실무 생산성과 타입 안정성까지 생각하면 Prisma도 매우 강력하다.
한 줄 요약:
- "JPA 감성으로 빠르게 익히기"는 TypeORM
- "명시적이고 깔끔한 데이터 접근"은 Prisma
현재 예제는 JWT + Guard + Role 기반으로 작성되어 있다.
관련 파일:
- auth.controller.ts
- auth.service.ts
- jwt.strategy.ts
- jwt-auth.guard.ts
- roles.guard.ts
- roles.decorator.ts
- http-exception.filter.ts
/auth/signup,/auth/login에서 JWT 발급JwtStrategy가 토큰에서 payload 해석JwtAuthGuard가 인증 여부 검사RolesGuard가@Roles()메타데이터를 읽어 권한 검사
Spring Security에서 자주 쓰는 "JWT 인증 + Role 기반 인가"를 NestJS에서도 비슷한 책임 분리로 가져가기 위해서다.
SecurityFilterChain+ JWT filter +@PreAuthorize("hasRole('ADMIN')")와 역할이 비슷하다.- 인증과 인가를 분리한다.
- NestJS는 Filter Chain보다 Guard 중심으로 생각하는 것이 자연스럽다.
- 권한 메타데이터는 커스텀 decorator로 읽는다.
JwtStrategy=UserDetailsService+ JWT parsing 일부 역할JwtAuthGuard= 인증 필터 느낌RolesGuard= 메서드 보안 검사기 느낌@CurrentUser()=@AuthenticationPrincipal과 비슷
main.ts에서ValidationPipe를 전역 등록해 Spring의@Valid와 비슷하게 요청 검증을 적용했다.HttpExceptionFilter는 Spring의@ControllerAdvice+@ExceptionHandler역할로 보면 된다.
관련 파일:
- product.controller.ts
- product.service.ts
- cart.controller.ts
- cart.service.ts
- order.controller.ts
- order.service.ts
- order-log.schema.ts
GET /api/productsGET /api/products/:productIdPOST /api/products
POST /api/products는 ADMIN 권한만 허용했다.
상품 조회는 공개, 상품 등록은 관리자 권한으로 제한하는 전형적인 커머스 규칙을 반영했다.
@GetMapping,@PostMapping,@PreAuthorize조합과 유사하다.
GET /api/cartPOST /api/cart/items
장바구니는 사용자별 1개를 기본으로 보고, 아이템을 추가하는 흐름으로 단순화했다.
처음 연습할 때는 장바구니 멀티 버전이나 쿠폰 결합까지 넣으면 복잡도가 너무 올라간다.
그래서 "회원별 단일 장바구니"라는 많이 쓰는 모델을 먼저 잡았다.
- Service 레이어에서 cart 조회 후 create or update 하는 패턴은 Spring에서도 흔하다.
POST /api/ordersGET /api/orders
주문 생성 흐름:
- 장바구니 조회
- 재고 검증
- 주문/주문아이템 저장
- 상품 재고 차감
- MongoDB에 주문 로그 기록
- 장바구니 비우기
이커머스 핵심은 "주문은 RDB 트랜잭션", "이력은 로그 저장소"의 분리다.
연습 프로젝트에서도 이 경계를 명확히 보는 것이 중요하다.
@Transactional서비스 메서드 안에서 주문 생성과 재고 차감을 묶는 사고방식과 비슷하다.
- 현재 예제 코드는 학습용이라 간단히 작성했다.
- 실무라면 TypeORM transaction manager나 query runner로 주문 생성과 재고 차감을 하나의 트랜잭션으로 묶어야 한다.
- Mongo 기록은 outbox/event-driven으로 분리하는 것도 좋다.
관련 파일:
.env.example을 복사해.env생성docker compose up -d postgres mongonpm installnpm run start:dev
- 멀티 스테이지 빌드 사용
- build stage에서 TypeScript compile
- runtime stage에서는 production dependency만 설치
NestJS는 결국 Node.js 앱이므로, Spring Boot jar 배포 대신 "컨테이너 이미지" 중심 운영이 더 자연스럽다.
- 결국 ECS에 올릴 때는 Spring Boot도 Docker 이미지로 다루는 경우가 많다.
- JVM 튜닝보다 Node 프로세스/메모리/이벤트 루프 관점 모니터링이 더 중요하다.
권장 흐름:
- GitHub Actions 또는 CodeBuild에서 테스트/빌드
- Docker 이미지 생성
- ECR push
- CodePipeline or CodeDeploy로 ECS 서비스 롤링 배포
권장 AWS 구성:
- ECS Fargate
- ALB
- ECR
- RDS PostgreSQL
- DocumentDB 또는 MongoDB Atlas
- CloudWatch Logs
- Secrets Manager
- Source: GitHub
- Build: CodeBuild
- Push: ECR
- Deploy: ECS
synchronize: true는 로컬 개발에서만 사용- 운영은 migration 기반으로 전환
- refresh token 분리
- Redis 캐시와 분산락 고려
- 주문은 트랜잭션 + 재고 동시성 제어 필요
- 로그/이력은 eventually consistent 구조 허용
- Spring JPA 감각으로 빨리 적응하고 싶을 때
- Entity 중심 설계가 더 편할 때
- 학습 초기에 "NestJS 구조"를 먼저 익히고 싶을 때
- 타입 안정성이 특히 중요할 때
- 스키마와 migration 흐름을 명확하게 관리하고 싶을 때
- 팀이 명시적인 쿼리 구조를 선호할 때
- 이 프로젝트는 "학습용 시작점"이다.
- 실무 수준으로 가려면 주문 트랜잭션, 재고 동시성, 결제, 쿠폰, 배송, 리뷰, 검색, 캐시를 확장해야 한다.
- 그래도 Spring Boot 개발자가 NestJS 감각을 잡기에는 좋은 최소 단위로 구성했다.
cp .env.example .env
docker compose up -d postgres mongo
npm install
npm run start:dev현재 이 워크스페이스에서는 node, npm이 설치되어 있지 않아 실제 빌드 검증은 수행하지 못했다.
로컬에 Node.js 20 이상을 설치한 뒤 위 명령으로 바로 실행하면 된다.
- 지금 예제를 TypeORM 기준으로 직접 실행해보기
- 주문 생성에 transaction 적용해보기
- Prisma 버전으로 repository 계층 다시 작성해보기
- Redis 캐시와 재고 분산락 추가해보기
- 결제 모듈과 이벤트 기반 주문 이력 확장하기
Swagger UI는 앱 실행 후 아래 경로에서 확인할 수 있다.
http://localhost:3000/docs
Spring Boot에서 springdoc-openapi를 붙여 Swagger UI를 보는 것처럼, NestJS도 @nestjs/swagger로 API 문서를 바로 노출하는 편이 개발 속도가 좋다.
DTO에 예시값을 넣고 Controller에 태그와 설명을 달아두면, 프론트엔드나 테스트할 때 훨씬 빠르게 확인할 수 있다.
- 비슷한 점:
@Operation,@Schema,@Tag같은 문서화 감각이 거의 같다. - 다른 점: NestJS는 DTO decorator와 controller decorator 조합으로 문서가 생성되고,
main.ts에서 SwaggerModule을 직접 부트스트랩한다.
/auth/signup또는/auth/login으로 JWT를 발급받는다.- Swagger UI 우측 상단
Authorize버튼을 누른다. Bearer <accessToken>형식으로 넣거나 토큰 값만 넣어 인증한다.- 보호된 API인
/api/cart,/api/orders,POST /api/products를 테스트한다.