NestJS Request lifecycle

2025. 3. 2. 14:59Framework/NestJS

이번 포스팅에서는 NestJs 프레임워크에서 Client로부터 Request가 들어오고 Response가 나가기까지의 전체 Request Cycle에 대해 작성해보려고 합니다. 해당 내용은 전부 NestJS 공식문서를 참고할 수 있지만, 일부 디테일한 설명을 추가했습니다. 추가적으로 해당 포스트는 NestJs의 Request lifecycle에 대한 개념적 이해와 철학에 대한 내용을 중심으로 작성했기에 구체적인 소스코드는 포함하지 않았음을 미리 말씀드립니다.

 


Request lifecycle

NestJs에서 client의 request가 들어오고 server의 response가 나가기까지 전체 과정은 다음과 같습니다.

 

  1. Client Request: 클라이언트가 HTTP 요청을 보냅니다. NestJs 서버가 요청을 수신하고 처리를 시작합니다. 
  2. Middleware 실행: 미들웨어는 요청이 Controller에 도달하기 전에 실행됩니다. 
    1. 전역 미들웨어(Globally Middleware): 애플리케이션 전역에서 등록된 미들웨어로 모든 요청에 대해서 적용됩니다. 
    2. 모듈 미들웨어(Module Bound Middleware): 특정 모듈 내부에서 사용이 정의되었을 때 호출되는 미들웨어입니다.
  3. Guards 실행: Middleware의 실행 이후에 실행됩니다. 주로 인증 및 권한 체크를 담당합니다.
    1. 전역 가드(Globally Guards)
    2. 컨트롤러 가드(Controller Guards): controller 수준에서 가드를 등록할 수 있습니다.
    3. 라우트 가드(Route Gurads): controller 하위의 라우트 단위로 가드로 등록할 수 있습니다. 즉, 같은 controller 내의 다른 라우트는 가드를 지정하지 않았을 경우 통과합니다. 
  4. Pre-Controller Interceptors 실행: 인터셉터는 미들웨어, 가드, 파이프와는 달리 요청과 응답 두 가지 사이드에서 호출됩니다. 자세한 설명은 아래에서 다루도록 하겠습니다. 
    1. 전역 인터셉터(Globally Interceptors, Pre-Controller)
    2. 컨트롤러 인터셉터(Controller Interceptors, Pre-Controller)
    3. 라우트 인터셉터(Route Interceptors, Pre-Controller)
  5. Pipes 실행
    1. 전역 파이프(Global Pipes)
    2. 컨트롤러 파이프(Controller Pipes)
    3. 라우트 파이프(Route Pipe)
    4. 경로 매개변수 파이프(Route Paramenter Pipes)
  6. Controller, Service 실행
  7. Post-Request Interceptors 실행: 응답이 생성된 후 요청 전과 반대 순서로 인터셉터가 다시 실행됩니다. 
    1. 라우트 인터셉터(Route Interceptors, Post-Controller)
    2. 컨트롤러 인터셉터(Controller Interceptors, Post-Controller)
    3. 전역 인터셉터(Globally Interceptors, Post-Controller)
  8. Exception Filters 실행: 요청 처리 중 오류가 발생하면 Exception Filters가 실행됩니다. 
    1. 라우트 예외 필터(Route Exception Filters)
    2. 컨트롤러 예외 필터(Controller Exception Filters)
    3. 전역 예외 필터(Global Exception Filters)
  9. Server Response: 모든 처리가 완료되면 NestJs 서버는 클라이언트에게 최종 응답을 보냅니다.

이제 각 Request Cycle의 구성요소에 대해 더 디테일하게 보겠습니다.


왜 Middleware가 가장 먼저 실행되도록 설계 됐을까? 

저는 4가지 구성요소 가운데 왜 미들웨어가 가장 먼저 실행되도록 설계되었을까?라는 궁금증을 갖게 되었습니다. 

NestJs는 NodeJs위에서 동작하는 백엔드 프레임워크입니다. NestJss는 Express.js를 기반으로 동작하지만, Angular에서 영감을 받아 모듈 시스템, 데코레이터(Decorator), 의존성 주입(DI)등을 지원하는 등 기능이 확장되었습니다. 

즉, NestJs는 NodeJs를 기반으로 동작하며 Express.js를 내부적으로 사용하고 있습니다. 

 

다만, Express.js와 다른 점은 Middleware를 제외한 Gurads, Interceptors, Pipes라는 개념이 없습니다. 즉, Express.js는 Middleware를 사용하거나 직접 기능을 구현해야 합니다. 넓게 보면 NestJs의 Guards, Inteceptors, Pipes는 Middleware에서 목적과 사용성에 맞게 파생 및 확장된 기능이라고 할 수 있습니다.

 

그래서 NestJs에서 미들웨어가 가장 먼저 실행되도록 설계된 이유를 생각해 봤을 때  애플리케이션 전체에서 공통된 기능이나 초기 요청 처리를 일관되게 적용하기 위함으로 미들웨어를 사용하고 요청, 응답 전처리, 인증 같은 특정 목적에 부합하는 하위 로직을 구현하기 위해 미들웨어 이후에 가드나, 인터셉터등을 사용한다고 예상할 수 있었습니다. 

 

예를 들어 로깅, CORS 설정, 정적 파일 서빙 등과 같은 작업은 모든 요청에 대해 일관되게 적용되어야 하는데 이러한 기능들은 미들웨어에서 처리함으로써 이후의 단계(가드나 인터셉터) 등에서는 이미 정제된 요청 데이터를 다루도록 함으로써 책임을 분리할 수 있습니다. 

Nest.js Docs

 

NestJs의 공식문서를 보면 NestJs의 모듈화 및 계층적 아키텍처의 철학을 Angular에서 많은 영감을 받았다고 합니다. 즉, 미들웨어는 애플리케이션의 가장 외곽 계층에 위치하여 애플리케이션의 다른 계층이 보다 구체적으로 세밀한 작업을 수행하기 전에 전반적인 설정과 전처리를 담당하도록 '기대'되고 있다는 것입니다.  

 

또한 이렇게 미들웨어, 가드, 인터셉터, 파이프 등 각 계층을 명확히 분리함으로써 명확한 '책임'을 줄 수 있습니다. 예를 들어 가드는 인증 및 권한 검증, 인터셉터는 요청/응답의 추가 가공, 파이프는 데이터 검증 및 변환을 담당하게 할 수 있습니다. 

 

결론적으로 NestJs에서 미들웨어를 가장 먼저 실행하도록 설계한 의도는 모든 요청에 대해 일관된 초기 처리를 수행하고 이후의 보안, 데이터 가공, 유효성 검사 등의 구체적 로직을 보다 명확하고 효율적으로 실행하기 위한 설계자의 의도가 투영됐다고 할 수 있습니다. 이러한 설계는 Angular로부터 영감을 받은 모듈화, 계층 분리, 책임 분리라는 NestJs의 철학에 기반합니다. 


Middleware vs Guards vs Inteceptors vs Pipes

NestJS에서 4가지 구성요소는 요청과 응답의 흐름을 제어하는 역할을 수행하지만, 각각의 동작 방식과 적용 범위가 다릅니다. 

 

1. Middleware

 

미들웨어는 일반적으로 요청 로깅, CORS 설정, 정적 파일 제공 등과 같이 애플리케이션 전체에 공통적인 초기 작업을 수행합니다.

위에서도 언급했듯이 미들웨어가 가장 먼저 실행되는 이유는 간접적으로 NestJs의 철학과 관련이 있지만, 직접적인 이유는 Express.js 같은 플랫폼 레벨에서 모든 HTTP 요청이 들어오도록 강제하고 있기 때문입니다. 

 

2. Gurads

 

가드는 애플리케이션의 비즈니스로직에 접근하기 전에 인증과 권한 검사를 주로 수행합니다. 즉, 사용자의 신원 및 권한을 확인하고 유효한 요청만 리소스에 접근가능하도록 막아주는 역할을 담당합니다. 

 

3. Interceptors

 

인터셉터는 요청이 컨트롤러에 도달하기 전과 응답이 클라이언트에 반환되기 전, 후처리를 담당하는 특수한 구성요소입니다. 주로 요청, 응답을 가로채서 추가적인 공통로직을 수행하는데 캐싱이나 로깅을 수행하는 것이 일반적입니다. 가드로 인해 신뢰 가능한 요청인지 아닌지는 신경 쓸 필요가 없으며 효율적으로 요청, 응답에 대한 추가적인 로직을 구현할 수 있습니다. 

 

4. Pipes

파이프는 Controller가 요청을 처리하기 바로 전에 데이터의 형식을 변화하고 유효성을 검사하는 역할을 담당합니다. 즉, Controller에 올바른 데이터를 처리하도록 보장하는 역할을 주로 담당합니다. 이렇게 하면 Controller는 해당 요청이 올바른 요청 양식을 지키고 있는지와 같은 검증은 신경 쓰지 않아도 됩니다. 

 

마지막으로, 4가지 구성요소를 역할, 실행위치, (요청) 응답 변환여부, 일반적인 사용예시에 대한 간단 표 정리(GPT)로 마무리하겠습니다. 긴 글 읽어 주셔서 감사합니다.