코딩일상

[Nest.js] nest.js 뿌수기 공식 docs 모조리 파헤치기[Interceptors] 본문

개발 공부/nest.js

[Nest.js] nest.js 뿌수기 공식 docs 모조리 파헤치기[Interceptors]

solutionMan 2025. 12. 29. 17:15
반응형

 

NestJS의 인터셉터는 요청(Request)과 응답(Response)의 흐름 사이에서 로직을 가로채고 변형할 수 있는 아주 강력한 도구입니다. AOP(관점 지향 프로그래밍) 기술을 기반으로 하며, 애플리케이션 전반에 걸쳐 공통된 로직을 깔끔하게 처리할 수 있게 해줍니다.


1. 인터셉터의 핵심 역할 

인터셉터는 단순히 흐름을 끊는 것이 아니라 다음과 같은 다양한 역할을 수행합니다:

  • 함수 실행 전/후 로직 추가: 로깅(Logging)이나 시간 측정 등에 활용됩니다.
  • 결과 변환 (Response Mapping): 컨트롤러가 반환한 데이터를 가공하여 일관된 형식으로 클라이언트에게 전달합니다. (예: { data: 결과값 })
  • 예외 변환 (Exception Mapping): 발생한 에러를 가로채서 특정 예외(Exception)로 변경합니다.
  • 스트림 오버라이딩 (Overriding): 특정 조건(예: 캐시가 있는 경우)에서 컨트롤러 실행을 건너뛰고 바로 응답을 보냅니다.

2. 요청 수명 주기와 실행 순서 

인터셉터는 요청이 들어오고 나가는 양방향에서 작동하며, 바인딩된 범위에 따라 순서대로 실행됩니다.

[실행 순서]

  1. Global 인터셉터 🌏
  2. Controller 인터셉터 🏢
  3. Method 인터셉터 🎯

Tip: 응답이 나갈 때는 반대로 Method → Controller → Global 순으로 거꾸로 처리됩니다. 양파 껍질 구조를 생각하면 이해하기 쉽습니다!


3. 주요 구현 포인트 

인터셉터는 intercept() 메서드를 구현해야 하며, ExecutionContext와 CallHandler를 인자로 받습니다.

  • ExecutionContext: 현재 실행 중인 클래스(getClass())와 메서드(getHandler())의 정보를 담고 있습니다.
  • CallHandler: handle() 메서드를 호출해야 실제 라우트 핸들러(컨트롤러)가 실행됩니다.

[주의사항: @Res() 사용 금지] ⚠️ 컨트롤러에서 @Res()를 사용하여 직접 응답을 보내면

Nest의 표준 응답 로직을 건너뛰기 때문에 인터셉터의 응답 변환 로직이 작동하지 않습니다.

만약 쿠키 설정 등이 필요해 반드시 사용해야 한다면 @Res({ passthrough: true }) 옵션을 사용해야 인터셉터와 공존할 수 있습니다.

 

⚖️ @Res() vs @Res({ passthrough: true }) 비교

팀원들과의 의사소통 미스를 방지하기 위해 이 차이점을 명확히 정리해두면 좋아요.

특징 @Res() (기본) @Res({ passthrough: true })
응답 주도권 개발자 (Express/Fastify 객체) 🕹️ NestJS 프레임워크 🏗️
인터셉터 작동 작동 안 함 (Response Mapping 불가) ❌ 정상 작동
데이터 반환 res.send() 등으로 직접 전송 📧 return 문 사용 가능 ↩️
주요 용도 프레임워크 기능을 완전히 벗어날 때 🛠️ 쿠키 설정 등 특정 객체 기능만 필요할 때 🍪

 

💡 실전 예시

팀원들과의 협업 중 쿠키를 설정해야 하는 상황이라고 가정해 볼게요.

@Get()
findAll(@Res({ passthrough: true }) res: Response) {
  res.cookie('key', 'value'); // (1) res 객체로 특정 작업 수행
  return { message: 'Hello' }; // (2) 값을 return하여 인터셉터가 작동하게 함
}

이렇게 하면 res 객체도 쓰고, 우리가 아까 배운 인터셉터의 map(data => ({ result: data })) 로직도 안전하게 실행됩니다!

 

더보기

1. ExecutionContext

ExecutionContext는 현재 실행 중인 요청의 **맥락(Context)**을 담고 있는 가방과 같습니다.

  • 주요 메서드와 반환값:
    • getClass<T>(): 요청을 처리 중인 컨트롤러 클래스 정보.
    • getHandler(): 실행될 라우트 메서드 정보.
    • switchToHttp(): HTTP 환경(Request, Response 객체 등)으로 전환하는 헬퍼.
  • 실무 활용 예제: 로깅 및 보안 🛠️
    • 메타데이터 추출: 특정 API에 @Roles('admin') 같은 커스텀 데코레이터가 붙어 있는지 확인하여 접근을 제어할 때 사용합니다.
    • 정밀한 로깅: context.getClass().name과 context.getHandler().name을 조합하면 "UserController - findOne 메서드 실행 중"과 같은 상세한 로그를 남길 수 있습니다.

========================================================================

@Injectable()
export class DetailLoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest(); // 1번 선택지: Request 객체 획득
    const className = context.getClass().name; // 컨트롤러 이름
    const handlerName = context.getHandler().name; // 메서드 이름

    console.log(`[시작] ${request.method} ${request.url} | ${className}.${handlerName}`);

    return next.handle().pipe( // 컨트롤러 실행
      tap(() => console.log(`[종료] ${className}.${handlerName}`))
    );
  }
}

========================================================================

[시작] GET /users/profile | UsersController.getProfile
[종료] UsersController.getProfile

4. RxJS를 활용한 고급 스트림 제어 

인터셉터는 next.handle()이 반환하는 Observable 스트림을 활용하여 강력한 기능을 구현합니다:

  • map(): 응답 데이터를 변형합니다.
  • catchError(): 예외 상황을 처리하고 에러를 변환합니다.
  • timeout(): 일정 시간 이상 응답이 없으면 에러를 발생시킵니다.
  • tap(): 데이터에 영향을 주지 않고 로깅 등의 사이드 이펙트를 실행합니다.

 


5. 바인딩 순서

바인딩 순서 및 우선순위

인터셉터는 적용된 위치에 따라 실행 순서가 결정됩니다.

순서 바인딩 수준 설정 방법
1 Global 🌏 app.useGlobalInterceptors() 또는 APP_INTERCEPTOR
2 Controller 🏢 클래스 레벨에 @UseInterceptors() 사용
3 Method 🎯 특정 라우트 메서드에 @UseInterceptors() 사용

 

 

  • 요청 시: Global → Controller → Method 순으로 실행됩니다.
  • 응답 시: Method → Controller → Global 순으로 거꾸로 나갑니다.

 

반응형
Comments