NestJS vs Express vs Spring — 언제 무엇을 선택할까
새 프로젝트를 시작할 때 "NestJS? Express? 아니면 Spring Boot?" 고민이 됩니다. 세 프레임워크 모두 REST API를 만들 수 있지만, 프로젝트 규모와 팀 구성에 따라 적합한 선택이 달라집니다. 어떤 기준으로 골라야 후회하지 않을까요?
한눈에 비교
| 기준 | Express | NestJS | Spring Boot |
|---|---|---|---|
| 언어 | JavaScript/TS | TypeScript | Java/Kotlin |
| 아키텍처 | 미니멀 (자유) | 모듈 + DI (구조화) | IoC + AOP (구조화) |
| 학습 곡선 | 낮음 | 중간 | 높음 |
| DI | 없음 | 내장 | 내장 (강력) |
| ORM | Sequelize, Prisma 등 | TypeORM, Prisma 등 | JPA/Hibernate |
| 타입 안전성 | 선택적 | 기본 (TS) | 기본 (Java/Kotlin) |
| 성능 (단순 I/O) | 높음 (이벤트 루프) | 높음 (Express 기반) | 중간 (스레드 기반) |
| 성능 (CPU 집약) | 낮음 (싱글 스레드) | 낮음 (싱글 스레드) | 높음 (멀티 스레드) |
| 생태계 | 가장 넓음 (npm) | npm + NestJS 모듈 | Maven/Gradle (엔터프라이즈) |
| 테스트 | 직접 구성 | Jest 내장 | JUnit + Spring Test |
| 배포 크기 | 수 MB | 수 MB | 수십~수백 MB |
| 콜드 스타트 | 빠름 (~100ms) | 빠름 (~200ms) | 느림 (~2-5초) |
아키텍처 비교
Express — 자유의 대가
app.js
├── app.get('/users', handler)
├── app.post('/users', handler)
└── 구조? 알아서 하세요
Express는 라우터와 미들웨어 만 제공합니다. 나머지(DI, 프로젝트 구조, 에러 처리 패턴)는 전부 개발자 몫입니다.
- 프로젝트 초반에는 빠르게 시작할 수 있습니다.
- 코드가 늘어나면 개발자마다 구조가 달라집니다.
- 팀이 커질수록 일관성을 유지하기 어렵습니다.
NestJS — 구조화된 Node.js
src/
├── app.module.ts ← 모듈 트리의 루트
├── user/
│ ├── user.module.ts ← 기능 모듈
│ ├── user.controller.ts
│ └── user.service.ts
└── auth/
├── auth.module.ts
└── auth.guard.ts
Express의 유연함 위에 모듈 시스템, DI, 데코레이터 패턴 을 얹었습니다. Spring에서 넘어온 개발자가 가장 적응하기 쉬운 Node.js 프레임워크입니다.
Spring Boot — 엔터프라이즈 표준
src/main/java/com/example/
├── config/
├── controller/
├── service/
├── repository/
└── entity/
가장 성숙한 생태계를 가지고 있습니다. 트랜잭션 관리, 보안, 배치 처리 등 엔터프라이즈에 필요한 거의 모든 것이 내장되어 있습니다.
성능 특성이 다르다
Node.js(Express, NestJS)와 JVM(Spring)은 근본적으로 다른 실행 모델을 가집니다.
I/O 바운드 작업 — Node.js 유리
Node.js (이벤트 루프):
요청 A → DB 쿼리 시작 → 대기 안 함 → 요청 B 처리 → 요청 A 결과 반환
Spring (스레드 풀):
스레드 1 → 요청 A → DB 쿼리 → 대기(블로킹) → 응답
스레드 2 → 요청 B → 처리 → 응답
DB 조회, 외부 API 호출 같은 I/O 대기가 많은 작업 에서는 Node.js의 이벤트 루프가 효율적입니다. 스레드 생성/전환 오버헤드가 없으니까요.
CPU 바운드 작업 — Spring 유리
이미지 처리, 복잡한 계산, 대용량 데이터 변환 같은 CPU를 많이 쓰는 작업 에서는 Node.js의 싱글 스레드가 병목이 됩니다. Spring은 멀티 스레드로 CPU 코어를 모두 활용합니다.
Node.js에서 CPU 집약 작업을 처리하려면 Worker Threads를 쓰거나 별도 서비스로 분리해야 합니다.
생태계와 도구
| 항목 | Express/NestJS | Spring Boot |
|---|---|---|
| 패키지 관리 | npm (~200만 패키지) | Maven Central (~50만) |
| 모니터링 | PM2, Datadog | Spring Actuator, Micrometer |
| API 문서 | Swagger (@nestjs/swagger) | SpringDoc / Swagger |
| 메시지 큐 | Bull (Redis 기반) | Spring AMQP, Kafka |
| 캐싱 | cache-manager | Spring Cache |
| 배치 | Bull Queue, Agenda | Spring Batch |
| gRPC | @nestjs/microservices | Spring gRPC |
Spring의 생태계가 엔터프라이즈 기능 에서 압도적으로 풍부합니다. 분산 트랜잭션, 배치 처리, 복잡한 보안 요구사항이 있다면 Spring이 유리합니다.
NestJS는 npm 생태계를 활용하면서 마이크로서비스 통신, WebSocket, GraphQL 같은 현대적 패턴을 공식 지원합니다.
언제 무엇을 선택할까
빠른 프로토타입, 소규모 API → Express
- 팀 1~2명, 빠른 MVP
- 구조보다 속도가 중요
구조화된 Node.js 백엔드 → NestJS
- 팀 3명 이상, 장기 프로젝트
- TypeScript 필수
- 프론트엔드 팀과 같은 언어 (풀스택 TS)
- 마이크로서비스, 이벤트 기반 아키텍처
엔터프라이즈, 대규모 시스템 → Spring Boot
- 복잡한 트랜잭션, 배치 처리
- 기존 Java/Kotlin 팀
- 강력한 타입 시스템 + 컴파일 타임 검증
- 금융, 결제 등 안정성 최우선
"둘 다" 가능한 경우
팀에 Java와 TypeScript 모두 가능한 개발자가 있고, 마이크로서비스 아키텍처라면 ** 서비스별로 다른 프레임워크 **를 쓰는 것도 방법입니다.
API Gateway: NestJS (빠른 라우팅, 가벼운 콜드 스타트)
인증 서비스: NestJS (JWT, Passport 생태계)
결제 서비스: Spring Boot (트랜잭션 안정성)
배치/정산: Spring Boot (Spring Batch)
실시간 알림: NestJS (WebSocket, 이벤트 루프)
주의할 점
"팀의 기술 스택"이 가장 중요한 기준이다
프레임워크 자체의 기술적 우열보다, ** 팀이 얼마나 잘 쓸 수 있는가 **가 프로젝트 성패를 좌우합니다.
Java 경험만 있는 팀이 NestJS를 도입하면 TypeScript + Node.js 런타임 + 비동기 패턴을 동시에 학습해야 합니다. 반대로 Node.js 팀이 Spring을 도입하면 JVM + Java/Kotlin + Spring의 방대한 추상화를 이해해야 합니다.
기술적 호기심이 아니라 ** 팀의 생산성 **을 기준으로 판단해야 합니다.
Node.js의 싱글 스레드 한계를 무시하면
Node.js에서 동기적으로 오래 걸리는 작업(큰 JSON 파싱, 이미지 리사이즈 등)을 실행하면 ** 이벤트 루프 전체가 멈춥니다.** 모든 요청이 대기하게 됩니다.
Express든 NestJS든 이 한계는 동일합니다. CPU 집약 작업이 핵심 요구사항이라면 Spring이 더 안전한 선택입니다.
정리
| 기준 | Express | NestJS | Spring Boot |
|---|---|---|---|
| 핵심 강점 | 단순함, 빠른 시작 | 구조 + Node.js 성능 | 엔터프라이즈 생태계 |
| 적합한 규모 | 소규모, 프로토타입 | 중소~중대규모 | 중대규모~대규모 |
| 최대 약점 | 구조 부재 | Node.js 싱글 스레드 한계 | 무거움, 느린 콜드 스타트 |
| 팀 요건 | JS/TS 기본 | TypeScript 숙련 | Java/Kotlin + Spring 이해 |
프레임워크 선택에 정답은 없습니다. 다만 "왜 이걸 선택했는가"에 대한 근거 는 있어야 합니다. 면접에서도, 실무에서도 이 근거가 기술적 판단력을 보여줍니다.