[NestJS 공식문서 정독하기] Overview - Interceptors

2022. 8. 3. 21:13Web/NestJS

반응형
🔗  https://docs.nestjs.com/interceptors
 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

Interceptors

  • interceptor는 NestInterceptor 인터페이스를 구현하며 @Injectable() 데코레이터가 적용되어 있는 클래스임
  • interceptor는 AOP에서 영감을 받은 여러가지 역할을 할 수 있음
    • 추가 로직을 메소드 호출 이전/이후에 바인딩
    • 함수의 반환값을 조작
    • 함수에서 던져진 예외를 조작
    • 기존 함수의 기능을 확장
    • 특정 함수를 완전 덮어씌우기 (용도: 캐싱 등등...)
  • 모든 interceptor는 intercept 메소드를 구현해야 함
  • 매개변수 ExecutionContext 는 guard의 그것과 동일함

Call handler

  • intercept 의 두번째 매개변수는 CallHandler 인터페이스이며 handle 메소드를 갖고 있음
  • handle 메소드는 route handler를 호출하는 함수임
  • 따라서, interceptor의 로직상의 특정 시점에 handler를 동작시킬 수 있으며 handle 메소드를 호출하지 않으면 handler를 무시할 수도 있음
  • handle 메소드는 rxjs의 Observable 타입을 반환하므로 이를 활용할 수 있음
  • AOP에서 route handler를 호출하는 것을 Pointcut이라고 함

Aspect interception

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}

Binding interceptors

@UseInterceptors(LoggingInterceptor)
export class CatsController {}
@UseInterceptors(new LoggingInterceptor())
export class CatsController {}
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}
  • pipe, guard와 동일하게 controller-scoped, method-scoped, global-scoped 모두 가능
  • 적용 방법 및 특성 또한 동일

Response mapping

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(map(data => ({ data })));
  }
}
  • handle 메소드에서 반환하는 Observable 은 route handler에서 처리한 데이터가 들어있음
  • map operator로 손쉽게 해당 데이터를 조작할 수 있음
  • 위 코드는 handler에서 반환한 데이터를 새로운 객체의 data라는 키에 매핑하는 interceptor임
  • intercept 메소드는 동기/비동기 모두 가능

Exception mapping

import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  BadGatewayException,
  CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(
        catchError(err => throwError(() => new BadGatewayException())),
      );
  }
}
  • rxjs의 catchError 메소드로 예외를 catch해서 조작할 수도 있음

Stream overriding

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';

@Injectable()
export class CacheInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const isCached = true;
    if (isCached) {
      return of([]);
    }
    return next.handle();
  }
}
  • 아예 handler 호출을 하지 않고 다른 데이터를 반환하는 케이스도 있음
  • 위와 같이 캐싱 처리를 하기 위해 캐싱된 데이터를 반환하는 경우는 handler를 스킵할 수도 있음

More operators

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      }),
    );
  };
};
  • 다양한 rxjs의 operator를 활용할 수 있음
  • 위와 같이 timeout operator를 활용해서 일정 시간 동안 응답이 없을 경우 error 처리를 할 수도 있음
반응형