안동민 개발노트 아이콘

안동민 개발노트

1장 : NestJS 소개와 기본 개념

NestJS의 특징과 아키텍처

Node.js 서버 개발을 해보면 초기에는 빠르게 만들 수 있지만, 기능이 늘어날수록 구조가 흔들리는 순간을 자주 만나게 됩니다.

라우팅, 비즈니스 로직, 인증, 예외 처리, 테스트 코드가 섞이기 시작하면 작은 변경에도 영향 범위를 예측하기 어려워집니다.

많은 팀이 이 시점에서 프레임워크를 바꿔야 하나?를 고민하게 되고, 그 대안 중 하나가 NestJS입니다.

NestJS는 기본 HTTP 어댑터로 Express를 사용할 수 있고, @nestjs/platform-fastifyFastifyAdapter를 선택해 Fastify 기반 서버로도 실행할 수 있습니다. 하지만 단순히 HTTP 프레임워크 위에 장식을 얹은 도구는 아닙니다. 대규모 서버 개발에서 필요한 구조와 규칙을 기본값으로 제공하는 프레임워크에 가깝습니다.

이 절에서는 NestJS가 왜 주목받는지, 그리고 실제로 어떤 아키텍처 원칙이 개발 생산성과 운영 안정성을 높이는지 현실적인 관점으로 정리하겠습니다.

NestJS는 Node.js 환경에서 효율적(Efficient)이고 안정적(Reliable)이며 확장성(Scalable) 있는 서버 애플리케이션을 구축하기 위한 진보적인(Progressive) Node.js 프레임워크입니다.

이 정의에 담긴 핵심 키워드는 효율성, 신뢰성, 확장성, 그리고 진보성입니다.

이 단어들이 실제 개발에서 어떤 의미를 갖는지, 지금부터 구조와 아키텍처 맥락에서 하나씩 풀어보겠습니다.


NestJS, 왜 선택해야 할까요?

Node.js 생태계에는 Express.js, Koa.js와 같은 웹 프레임워크가 이미 많이 존재합니다. NestJS는 이들과 달리 데코레이터 메타데이터, IoC/DI 컨테이너, 모듈 그래프를 기본 전제로 두며, 이 절에서는 그 구조 차이가 프로젝트 경계에 어떤 영향을 주는지 봅니다.

가장 큰 이유는 아키텍처의 견고함에 있습니다.

기존 Node.js 프레임워크들이 비교적 자유로운 개발 스타일을 제공한 반면, NestJS는 모듈화(Modularity)되고 구조화된 아키텍처를 지향합니다.

이 접근은 애플리케이션 규모가 커질수록 더 큰 효과를 냅니다.

정해진 규칙과 명확한 역할 덕분에 코드 복잡도를 낮추고, 장기 유지보수와 협업 환경에서의 일관된 코드 품질 유지가 쉬워집니다.

또한, NestJS는 TypeScript를 기반으로 구축되었습니다. TypeScript는 JavaScript에 정적 타입을 추가해 컴파일 단계에서 타입 불일치 일부를 줄이고, 데코레이터와 생성자 주입 패턴을 코드 구조 안에 드러내기 좋습니다. 이는 대규모 애플리케이션에서 의존성 경계와 테스트 대역을 관리할 때 특히 중요한 요소입니다.


핵심 아키텍처: 모듈, 컨트롤러, 프로바이더

NestJS는 내부적으로 모듈(Modules), 컨트롤러(Controllers), 프로바이더(Providers)라는 세 가지 핵심 구성 요소를 기반으로 애플리케이션을 구축합니다. @Module()controllers, providers, imports, exports 메타데이터가 모듈 스코프와 공개 범위를 만들고, DI 컨테이너가 이 그래프를 기준으로 인스턴스를 연결합니다.

  • 모듈 (Modules): NestJS 애플리케이션의 기본 구성 단위입니다. @Module() 데코레이터로 정의하며, 관련 컨트롤러와 프로바이더를 묶어 기능별 응집도를 높여줍니다. 예를 들어 사용자 인증 기능(로그인, 회원가입, 비밀번호 찾기 등)은 AuthModule 하나로 묶을 수 있습니다. 모듈은 다른 모듈을 가져오거나(imports), 자신의 프로바이더를 내보낼 수 있어(exports) 애플리케이션 전체 구조를 명확하게 정의할 수 있습니다.

  • 컨트롤러 (Controllers): 클라이언트 요청(Request)을 처리하고 응답(Response)을 반환하는 경계입니다. @Controller()@Get(), @Post() 같은 route decorator로 엔드포인트를 정의하며, DTO와 pipe로 입력을 정리한 뒤 비즈니스 로직을 처리할 프로바이더를 호출합니다. 컨트롤러는 가능한 한 얇게 유지하고, 분기와 외부 연동은 서비스 계층으로 넘기는 편이 테스트와 변경 대응에 유리합니다.

  • 프로바이더 (Providers): NestJS 애플리케이션의 대부분 비즈니스 로직을 담는 클래스입니다. @Injectable() 데코레이터로 정의하며, 서비스(Service), 레포지토리(Repository), 팩토리(Factory), 헬퍼(Helper) 등 다양한 형태로 존재합니다. 프로바이더는 의존성 주입(Dependency Injection) 메커니즘으로 다른 프로바이더나 컨트롤러에 주입되어 사용됩니다. 이 방식은 코드 재사용성을 높이고 테스트를 쉽게 하며 코드 간 결합도를 낮추는 데 크게 기여합니다. 테스트에서는 overrideProvider()나 mock provider를 사용해 외부 API, DB, 캐시 같은 의존성을 대체할 수 있습니다.

이 세 가지 구성 요소가 어떻게 상호작용하는지 간단한 그림으로 살펴보겠습니다.

NestJS는 모듈, 컨트롤러, 프로바이더를 분리해 관심사의 분리(Separation of Concerns) 원칙을 구조로 표현합니다. 각 구성 요소의 책임이 명확해지면 유지보수와 테스트가 쉬워집니다.

운영 관점에서도 이 분리는 큰 이점을 줍니다. 관심사가 분리된 구조에서는 로깅, 모니터링, 예외 정책, 재시도 정책을 계층별로 일관되게 적용하기가 쉬워집니다. 결국 NestJS의 강점은 코드를 예쁘게 만든다를 넘어, 장애 대응과 변경 대응 속도를 함께 높여준다는 데 있습니다.

아래 다이어그램은 NestJS의 구조화가 변경 대응, 테스트, 운영 정책으로 이어지는 경로를 정리합니다.


NestJS의 특징과 아키텍처에 대한 첫 번째 설명을 마무리하겠습니다. 여기까지 NestJS의 특징과 기본 아키텍처를 정리했습니다.

다음 절에서는 NestJS 개발 환경을 설정하는 방법에 대해 알아보겠습니다.