NestJS는 Request를 순차적으로 처리하여 응답을 생성한다.
이러한 처리 순서가 궁금했는데 이 글을 통해 알아보자!
이러한 처리 순서를 모르고 요청 처리를 하다보면 언젠가는 해당 부분에서 막힐 일이 생길 것이다.
요청에는 middleware, guard, interceptor, pipe, filter가 속해있고 우선 각각의 개념들에 대해 충분히 이해하고 라이프사이클에 대해 알아야 할 것이다.
미들웨어란 기본적으로 클라이언트의 요청을 처리하기 전에 수행되는 컴포넌트이다.
NestJS의 middleware는 express의 middleware와 동일하다.
요청을 받고 요청을 처리하고 next() 함수를 호출, 반드시 next() 호출
💥단, NestJS에서는 어떤 route에 middleware를 적용시킬지 지정할 수 있다. => consumer
route와 HTTP METHOD 지정도 가능하고
모든 route와 HTTP METHOD 지정도 가능 => main.ts bootstrap에서
guard(가드)는 요청을 다음 단계로 진행할지 말지 결정해주는 클래스이다.
주로, 인증(Authentication)과 인가(Authorization)을 위해 사용한다.
미들웨어에서도 인증과 인가처리를 할 수 있지만 미들웨어는 실행 컨텍스트에 접근하지 못하기 때문에
다음에 어떤 핸들러가 실행될지 알 수 없고 가드는 실행 컨텍스트 인스턴스에 접근할 수 있어
다음에 실행될 작업을 정확히 알고 있다.
true => 다음으로 진행
false => 여기서 멈춘다.
추후에 무료 유저, 요금제 사용 유저, 관리자 등으로 유저를 나눠서 사용
인터셉터는 AOP(관점 지향 프로그래밍)에서 영감을 받아 만들어진 클래스이다.
다음과 같은 기능들을 사용할 수 있고
기본적으로 인터셉터를 사용하여
사용자 상호 작용을 기록(사용자 호출 저장, 비동기 적으로 이벤트 디스패치 또는 타임 스탬프 계산)한다.
인터셉터의 실행 순서는 요청(전역 > 컨트롤러 > 라우터)에서는 가드와 비슷하지만
응답에서는 요청과 반대로 라우터 > 컨트롤러 > 전역 순으로 동작한다.
파이프는 @Injectable() 데코레이터로 주석이 달린 클래스
data transformation과 data validation을 위해 사용
컨트롤러 경로 처리기에 의해 처리되는 인수에 의해 작동
메소드가 호출 되기 직전에 파이프를 삽입하고 파이프는 메소드로 향하는 인수를 수신하고 이에 대해 작동
🔃Data Transformation
입력 데이터를 원하는 형식으로 변환
숫자를 받길 원하는데 문자열 형식으로 온다면 파이프에서 숫자로 변환해준다.
✅Data Validation
입력 데이터를 평가하고 유효한 경우 변경되지 않은 상태로 전달.
그렇지 않으면 데이터가 올바르지 않을 때 예외 발생!!
Ex. 비밀번호는 최소 8자 이상이어야 하는데 6자가 들어왔다. => 예외처리
파이프는 변환 및 valid 둘 다 해준다.
Handler level Pipe
@UsePipes() 데코레이터를 이용해서 사용
이 파이프는 모든 파라미터에 적용 된다.
Parameter Level Pipe
특정 파라미터에만 적용이 되는 파이프.
Global level Pipe
클라이언트에서 들어오는 모든 요청에 적용
가장 상단 영역인 main.ts에 넣어준다.
ValidationPipe, ParseIntPipe, ParseBoolPipe, ParseArrayPipe, ParseUUIDPipe, DefaultValuePipe
파이프를 이용한 유효성 체크
class-validator, class-transformer
커스텀 파이프 구현
PipeTransform이란 인터페이스를 새롭게 만들 커스텀 파이프에 구현
transform() 메소드가 필요
transform() 메소드
value, metadata 파라미터가 있는데
value = 처리가 된 인자의 값
metadata = 인자에 대한 메타데이터를 포함한 객체
하나의 컨트롤러에서 데이터에 대해 변환 및 유효성 검증을 하다보면 유지보수 시에 까다로운 경우가 생기는데
Ex. 중간만 변환 또는 유효성 검증 부분을 제거하거나 변경할 때 코드 변경의 복잡성이 올라간다.
Pipe 패턴을 사용하게 되면 각각의 파이프가 Input => Output 형태로 연결되기 때문에
결합성도 낮고 유지보수성도 높아진다.
https://docs.microsoft.com/en-us/azure/architecture/patterns/pipes-and-filters
예외처리 할 때 사용하는 클래스
프로그래밍을 하다 보면 항상 예외처리를 해줘야하는데 중복되는 예외처리가 많고
이를 모든 코드에 넣을 수 없기 때문에 따로 분리해서 사용한다.
try/catch로도 잡지 못한 예외가 발생하면 나머지 lifecycle이 무시되고 필터로 바로 건너뛴다.
filter도 각 함수마다 적용할 수 있고, 컨트롤러 위에도 적용 가능 => @UseFilters
전역에서도 적용 가능! => app.useGlobalFilters()
Incoming request
Globally bound middleware
Module bound middleware
Global guards
Controller guards
Route guards
Global interceptors (pre-controller)
Controller interceptors (pre-controller)
Route interceptors (pre-controller)
Global pipes
Controller pipes
Route pipes
Route parameter pipes
Controller (method handler)
Service (if exists)
Route interceptors (post-request)
Controller interceptors (post-request)
Global interceptors (post-request)
Exception filters (route, then controller, then global)
Server response