Node.js + Express REST API 서버 구축 실전 가이드
좋은 Express 서비스는 프레임워크가 가볍기 때문에 좋은 것이 아니라, 그 가벼움을 통제할 규칙을 팀이 의식적으로 만들었기 때문에 좋습니다.
계층 책임을 분명히 해야 합니다
실무에서는 보통 다음 정도의 분리가 건강한 기본선이 됩니다.
- route: URL과 middleware 조합
- controller: HTTP 요청/응답 변환
- service: 유스케이스와 비즈니스 규칙
- repository 또는 gateway: 저장소/외부 연동
- middleware: 공통 HTTP 관심사
중요한 것은 폴더 이름보다, 각 책임이 여러 곳으로 새지 않게 유지하는 것입니다.
Controller는 얇게 유지해야 합니다
Controller는 다음 세 가지에 집중할 때 가장 건강합니다.
- 검증된 입력을 읽기
- 올바른 유스케이스 호출
- 결과를 HTTP 응답으로 변환
반대로 SQL, 트랜잭션 오케스트레이션, 세부 권한 정책, 하위 시스템 retry 동작까지 떠안기 시작하면 유지보수 비용이 급격히 올라갑니다.
얇은 controller는 테스트를 쉽게 만들고, HTTP 관심사를 도메인 판단과 분리해줍니다.
Middleware는 강력하지만 과용되기 쉽습니다
Express middleware는 매우 편리하지만, 그래서 더 쉽게 남용됩니다.
middleware가 잘 맞는 역할은 다음과 같습니다.
- request logging과 request ID 부여
- 인증 컨텍스트 추출
- HTTP 경계의 입력 검증
- rate limiting
- 공통 오류 처리
반대로 도메인 문맥과 여러 downstream 의존성을 요구하는 비즈니스 흐름은 middleware보다 service에 두는 편이 더 읽기 쉽고 추적도 쉽습니다.
검증은 경계에서 멈춰야 합니다
운영 API는 최소한 다음을 분리해야 합니다.
- 입력 형식 검증
- 권한 검증
- 비즈니스 규칙 검증
입력 형식은 HTTP 경계에서, 비즈니스 규칙은 service 계층에서 다뤄야 상태 코드와 오류 모델이 일관됩니다.
인증은 인가를 대체하지 않습니다
많은 Express 코드베이스가 JWT 검증이나 세션 파싱까지만 구현하고 보안 설계를 끝낸 것처럼 다룹니다. 하지만 그것은 신원 확인일 뿐, 접근 권한 판정은 아닙니다.
실제 애플리케이션은 여전히 다음이 필요합니다.
- 역할 기반 접근 제어
- 리소스 소유권 검사
- tenant 경계
- 테스트 가능한 policy 결정
이 분리가 없으면 route handler 안에서 사용자 identity, 쿼리, 정책이 뒤섞이게 됩니다.
오류 모델은 초기에 표준화하는 편이 낫습니다
클라이언트와 운영자는 모두 예측 가능한 오류 구조에서 이득을 봅니다.
좋은 오류 정책은 보통 다음을 포함합니다.
- 일관된 error shape
- 안정적인 애플리케이션 에러 코드
- request ID 또는 correlation ID
- 사용자 메시지와 내부 상세의 분리
컨트롤러마다 오류 형식이 다르면 API 소비자도 어렵고 운영 디버깅도 어려워집니다.
운영 기본기는 뒤로 미루지 않는 편이 좋습니다
작은 Express 서비스라도 최소한 다음은 초기에 정하는 편이 낫습니다.
- graceful shutdown 동작
- request timeout 정책
- payload size 제한
- health endpoint
- structured logging
Express는 로컬 성공을 너무 쉽게 만들어주기 때문에 이런 기준을 뒤로 미루기 쉽지만, 나중에 붙이면 비용이 더 큽니다.
흔한 실수
실무에서는 다음 패턴이 자주 문제를 만듭니다.
- route나 controller 안에 persistence 로직이 직접 들어감
- middleware 체인 안에 비즈니스 판단이 숨어 있음
- 엔드포인트마다 검증 방식이 제각각임
- JWT 검증을 authorization policy의 대체로 사용
- 공유 오류 모델과 관측 기준이 없음
이런 문제는 한 번에 크게 생기기보다, 점진적으로 쌓이다가 나중에야 변경 난이도로 드러나는 경우가 많습니다.
마무리
좋은 Express 서비스는 프레임워크가 가볍기 때문에 유지되는 것이 아닙니다. 얇은 controller, 분리된 service, 절제된 middleware, 일관된 운영 규칙이 있을 때 Express의 장점이 오래 갑니다.
그 기준이 있어야 빠른 시작이 장기 생산성으로 이어집니다.
Continue Reading
다음으로 읽기 좋은 글
GraphQL API 설계와 구현: REST와 무엇이 다른가
GraphQL을 단순 필드 선택 기능이 아니라 API 계약 방식의 변화로 보고, 스키마 설계, resolver 책임, N+1, mutation, 권한, 운영 트레이드오프를 정리합니다.
⚙️ Backend비동기 작업 API의 멱등성과 상태 제어
긴 작업을 HTTP 요청 하나로 끝내지 않고 작업 생성, 재시도, 취소, 결과 조회까지 안정적으로 설계하는 방법을 정리합니다.
💬 LanguageTypeScript Utility Types 실전 가이드
TypeScript 유틸리티 타입을 DTO, 업데이트 payload, selector, 파생 타입 설계에 어떻게 써야 하는지, 어디서부터는 가독성을 해치는지 정리합니다.
💬 LanguageModern JavaScript ES2024 문법 실전 해설
최신 JavaScript 문법을 단순 문법 소개가 아니라, 코드 품질을 실제로 높이는지 기준으로 읽습니다. 무엇이 진짜 도움이 되고 무엇은 여전히 절제가 필요한지 정리합니다.
다음 탐색