2023. 6. 5. 13:30ㆍ개발 문서 번역/NestJS
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.
Interceptor란 무엇인가요?
가로채는 사람(것) 이라는 뜻입니다.
이는 관점 지향 프로그래밍(Aspect Oriented Programming, 剖面導向程式設計)에 영감을 받아 만들어졌습니다.
원래 있던 기능의 확장을 지원합니다.
특징은 아래와 같습니다.
- Controller의 메서드를 실행하기 전/후의 요청을 가로채 응답을 참조하거나 가공할 수 있습니다.
- Controller의 메서드 실행 전에 발동되는 인터셉터는 Pipe 실행 전에 발동됩니다.
- Middleware 실행 이후에 실행됩니다.
- 데이터와 Exception을 수정할 수 있습니다.
Interceptor 만들기
Interceptor는 CLI를 통해 생성이 가능합니다.
$ nest generate interceptor <INTERCEPTOR_NAME>
주의: <INTERCEPTOR_NAME>은 경로를 포함할 수 있습니다. ex: interceptors/hello-world
경로를 포함하여 생성시 src 폴더 안에 경로를 포함하여 Interceptor가 생성됩니다.
HelloworldInterceptor를 interceptors 폴더 안에 생성해보겠습니다.
$ nest generate interceptor interceptors/hello-world
src 폴더 내에 interceptors 라는 폴더가 보입니다.
안에는 hello-world.interceptor.ts와 hello-world.interceptor.spec.ts가 들어있습니다.
Interceptor의 뼈대가 생성되었습니다.
Interceptor에도 @Injectable 데코레이터의 class가 구현되어있는것을 확인할 수 있습니다.
그러나 Interceptor는 NestInterceptor의 인터페이스와 intercept(context: ExecutionContext, next: CallHandler) 메서드를 반드시 구현해야 합니다.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class HelloWorldInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle();
}
}
CallHandler
CallHandler는 Interceptor의 중요한 구성원입니다.
handle() 메서드의 구현을 통해 라우팅 처리 메서드를 호출하고 해당하는 Controller 메서드로 진입시킵니다.
즉 매 Interceptor에서 CallHandler의 handle()을 반환하지 않을 시 라우팅 처리는 작동하지 않습니다.
CallHandler는 intercept의 파라미터이므로 반드시 intercept 내에서 호출됩니다.
다시말해 handle()을 반환하기 전의 로직을 작성하여 Controller 메서드에 진입 전에 실행할 수 있습니다.
또한 handle()은 Observable을 반환하기 때문에 이 반환값을 pipe를 사용하여 조정하여 Controller 실행 후의 로직을 처리할 수 있습니다.
주의: handle()은 Observable이며 intercept의 반환값으로 사용함으로써
Nest가 해당 Observable을 subscribe하여 내부 로직을 실행할 수 있도록 합니다.
*Observable의 특징은 subscribe하지 않으면 내부 로직이 실행되지 않습니다.
이것이 handle()을 반환하지 않을 시 라우팅 처리가 작동에 실패하는 원인입니다.
ExecutionContext
ExecutionContext는 ArgumentHost의 class를 상속받았기 때문에 요청과 관련된 더 많은 정보를 제공할 수 있습니다.
아래는 ExecutionContext가 제공하는 두개의 메서드를 소개합니다.
이 두가지 메서드를 통해 애플리케이션의 유연성을 크게 높일 수 있습니다.
Controller Class 받아오기
getClass()를 통해 현재 요청에 해당하는 Controller Class를 가져올 수 있습니다.
const Controller: TodoController = context.getClass<TodoController>();
Controller Method 받아오기
getHandler()를 통해 현재 요청에 해당하는 Controller Method를 가져올 수 있습니다.
예를 들어 현재 요청이 TodoController의 getAll()을 호출할 시 이 메서드는 getAll 함수를 반환할 것입니다.
const method: Function = context.getHandler();
Interceptor 사용하기
사용 전에 먼저 hello-world.intercpetor.ts를 수정해봅시다.
Interceptor에 들어오면 Hello World!를 출력하고 변수를 사용해 Interceptor 진입 시간을 저장한 후 tap을 통해 빠져나가는 시간과 진입 시간의 시간 차를 구해보겠습니다.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class HelloWorldInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Hello World!');
const input = Date.now();
const handler = next.handle();
return handler.pipe(
tap(() => console.log(`${ Date.now() - input } ms`))
);
}
}
Interceptor를 수정했다면 한번 사용해봅시다. @UseInterceptors 데코레이터를 통해 쉽게 적용할 수 있습니다.
사용하는 방식은 크게 두가지가 있습니다.
1. 단일 Resource: Controller의 메서드 안에 @UseInterceptors 데코레이터를 적용시킵니다.
이는 해당 Resource에만 적용됩니다.
2. Controller: Controller에 @UseInterceptors 데코레이터를 적용시킵니다.
이는 해당 Controller 안의 모든 Resource에 적용됩니다.
아래는 Controller를 적용시킨 app.controller.ts의 예제입니다.
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { AppService } from './app.service';
import { HelloWorldInterceptor } from './interceptors/hello-world.interceptor';
@Controller()
@UseInterceptors(HelloWorldInterceptor)
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
http://127.0.0.1:3000에 들어가보면 터미널에 아래와 비슷한 결과가 찍힐겁니다.
Hello World!
3 ms
전역 Interceptor
공용 Interceptor를 모든 Resource에 전부 적용 시키고 싶다면
main.ts에 useGlobalInterceptors를 통해 전역 Interceptor를 설정할 수 있습니다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HelloWorldInterceptor } from './interceptors/hello-world.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new HelloWorldInterceptor());
await app.listen(3000);
}
bootstrap();
의존성 주입을 통해 전역 Interceptor 구현하기
위의 방법은 모듈 외부에서 전역 구성을 완료하는 방법입니다.
Pipe와 똑같이 의존성 주입을 통해 구현할 수 있으며
Provider의 token을 APP_INTERCEPTOR로 지정해주어 구현할 수 있습니다.
또한 useClass를 사용해 인스턴스화할 클래스를 지정해줍니다.
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HelloWorldInterceptor } from './interceptors/hello-world.interceptor';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: HelloWorldInterceptor
}
]
})
export class AppModule {}
마치며
Interceptor는 Controller를 수정하지 않고도 그 로직을 확장시킬 수 있습니다. 매우 편리한 기능이죠?
오늘 배운 것들을 요약하며 포스팅을 마치도록 하겠습니다.
1. Interceptor는 Middleware 이후에 실행되지만 Pipe와 Controller 실행 전/후에 실행될 수 있습니다.
2. Interceptor를 사용하면 원래 로직을 변경하지 않고도 로직을 확장하는것이 가능해집니다.
3. CallHandler는 Interceptor의 중요한 구성원입니다. handle()을 호출하여 라우팅 이 동작할수 있도록 해야합니다.
4. ExecutionContext는 getClass()와 getHandler() 메서드를 제공하여 유연성을 증가시켜줍니다.
5. 전역 Interceptor는 의존성 주입을 통해 구현할 수 있습니다.
'개발 문서 번역 > NestJS' 카테고리의 다른 글
NestJS 帶你飛! 시리즈 번역 14# Custom Decorator (0) | 2023.06.07 |
---|---|
NestJS 帶你飛! 시리즈 번역 13# Guard (0) | 2023.06.06 |
NestJS 帶你飛! 시리즈 번역 11# Middleware (0) | 2023.06.04 |
NestJS 帶你飛! 시리즈 번역 10# Pipe (하) (0) | 2023.06.03 |
NestJS 帶你飛! 시리즈 번역 09# Pipe (상) (0) | 2023.06.02 |