Research/Nest.js

nestjs_횡적 부가기능을 위한 interceptor

RIEM 2023. 4. 4. 19:53
728x90

https://docs.nestjs.com/interceptors

Interceptor란

Interceptor는 AOP(Aspect Oriented Programming)에서 영감을 받은 기술이다. AOP는 관점지향 프로그래밍 패러다임을 의미하는데, 부가 기능을 모듈화하여 코드의 재사용성을 극대화하는 방법이다.

Intercepts는 이러한 일들을 할 수 있다.

- 메소드 실행 전/후 추가적인 로직을 적용하고싶을 때
- 함수에서 반환된 결과값을 변형하고 싶을 때
- 함수에서 발생한 예외를 변형하고 싶을 때
- 기본적인 함수의 기능을 확장시키고 싶을 때
- 조건에 따라 함수를 오버라이딩하고싶을 때. (ex: 캐싱)

Interceptor은 @Injectable() 데코레이터로 주석을 달 수 있다. 즉, 의존성 주입이 가능하다는 말이다.

 

전체 요청 생애 주기 내 Interceptor의 위치

nest.js의 request의 생에 주기는 이렇다. 

  1. Incoming request
  2. Globally bound middleware
  3. Module bound middleware
  4. Global guards
  5. Controller guards
  6. Route guards
  7. Global interceptors (pre-controller)
  8. Controller interceptors (pre-controller)
  9. Route interceptors (pre-controller)
  10. Global pipes
  11. Controller pipes
  12. Route pipes
  13. Route parameter pipes
  14. Controller (method handler)
  15. Service (if exists)
  16. Route interceptor (post-request)
  17. Controller interceptor (post-request)
  18. Global interceptor (post-request)
  19. Exception filters (route, then controller, then global)
  20. Server response

https://docs.nestjs.com/faq/request-lifecycle

 

Interceptor 생성

// common/interceptors/success.interceptor.ts

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

@Injectable()
export class SuccessInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // Pre-controller interceptor stage
    console.log('Before...');

    const now = Date.now();
    // Post-request interceptor stage
    return next
      .handle()
      .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)));
  }
}

공식문서에서 제공하는 템플릿 인터셉터 파일을 만들었다. 인터셉터는 여러 모듈에서도 사용하는 일종의 횡적으로 기능을 수행하기 때문에 특정 모듈 내 종속시키는 것 보다는 따로 common 폴더를 만들어서 관리를 하면 좋다.

Interceptor 적용

...
@Controller('cats')
@UseInterceptors(SuccessInterceptor)
@UseFilters(HttpExceptionFilter)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getAllCat() {
    // Node.js : throw new Error
    // throw new HttpException('api is not available 😭', 401);
    return 'all cat';
  }

  @Get(':id')
  getOneCat(@Param('id', ParseIntPipe, PositiveIntPipe) param) {
    console.log(typeof param);
    return 'one cat';
  }

  ...

cats 컨트롤러에 SuccessInterceptor를 Intercept를 붙였다.

비즈니스 로직에서 number을 반환하고 이 전과 후에 Intercept가 모두 수행된 것을 알 수 있다.

 

전체 생애주기에서 interceptor가 크게 2 영역으로 구분되는데, 이 두 영역이 실제 코드에서는 Before과 After 부분에 해당한다.

 

Intercept 변형

data는 그대로두고 success 프로퍼티만 추가하고 싶다면 어떻게 해야할까? success 성공 여부 데이터를 추가하면 프론트엔드 개발자가 편하게 응답 데이터를 이해할 수 있을 것 같다. controller 반환값에 다른 프로퍼티를 추가하는 intercept로 커스터마이징 해보자.

// common/interceptors/success.interceptor.ts

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

@Injectable()
export class SuccessInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // Pre-controller interceptor stage
    console.log('Before...');

    // Post-request interceptor stage
    // Controller return값 변환 방식 변경
    return next.handle().pipe(
      map((data) => ({
        success: true,
        data: data,
      })),
    );
  }
}

map은 rxjs 라이브러리의 메소스다. data에 success, data 프로퍼티를 추가해주었다. success를 추가한 이유는 프론트 단이 이 메시지를 보고 응답 결과를 판단하기 쉽게 하기 위함이다.

...

@Controller('cats')
@UseInterceptors(SuccessInterceptor)
@UseFilters(HttpExceptionFilter)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get()
  getAllCat() {
    // Node.js : throw new Error
    // throw new HttpException('api is not available 😭', 401);
    console.log('getAllCat controller hanlder :D');
    return { cats: 'get all cat please!' };
  }

  @Get(':id')
  getOneCat(@Param('id', ParseIntPipe, PositiveIntPipe) param) {
    console.log(typeof param);
    return 'one cat';
  }
  ...

컨트롤러 단의 코드를 보면 getAllCat은 객체를, getOneCat은 단순 문자열만 반환한다. 이들이 interceptor을 통해 success 프로퍼티와 함께 반환 되면 어떻게 되는지 보자.

data는 동일하되 success 프로퍼티가 잘 추가되었다.

728x90