Nest Request LifeCycle
Nest에서 request가 오고 나서 부터 response를 주기까지의 lifecycle에 관한 글입니다.
사용자 인증이나 parameter의 검증 등 필요한 메소드들을 적절한 단계에서 사용하면 앱은 더욱 견고해 질것입니다.
Middleware, Guard, Interceptor, Pipe에 대한 지식이 선행되어 있으면 더욱 좋습니다.
먼저 큰 틀에서 보면
MiddleWare -> Guard -> Interceptor -> Pipe -> Controller -> Interceptor 입니다.
아래의 그림에서는 guard에서 부터 예외 상황이 발생할시에는 @useCatch로 exception filter에서 처리할 수 있습니다.
exception filter를 global로 설정하여 원하는 동작을 수행할 수 도 있습니다.
그림을 보면 각 단계에서 global, controller, route로 세분화 해서 나누어지는 것을 확인할 수 있습니다.
이 부분을 코드레벨에서 확인해 보겠습니다.
아래 사진은 테스트를 위해서 출력한 각 단계에서의 로그 입니다.
각 단계별로 적용하는 예시를 코드 레벨에서 살펴보겠습니다.
예시에 사용한 컨트롤러는 notifications로 하겠습니다.
MiddleWare
이전 글에서 middleware를 쓰기위한 방법을 알아보았습니다.
사용할수 있는 방법은 3가지 입니다.
- 전체 route에 적용
- 특정 route에 적용
- main.ts에서 함수 형식으로 전체 적용
1,2번은 app.module에서 작성하고 3번은 1,2번의 미들웨어와 작성하는 방식이 다릅니다.
1,2번 작성하는 방식부터 적용하는 방식까지 알아보겠습니다.
적절한 위치에 middleware 파일을 생성해 줍니다
// global.middleware.ts
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class GlobalMiddleware implements NestMiddleware {
private logger = new Logger('HTTP');
use(request: Request, response: Response, next: NextFunction): void {
console.log('this is global middleware');
next();
}
}
// app.module.ts
...
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(GlobalMiddleware).forRoutes('*'); // 전체 경로 적용
consumer.apply(CustomMiddleware).forRoutes(NotificationsController); // notification 컨트롤러에 속한 경로에 적용
}
}
함수로 생성해서 main.ts내에서 사용하기 위한 방법입니다.
// function.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function functionMiddleware(
req: Request,
res: Response,
next: NextFunction,
) {
console.log(`this is functionMiddleware`);
next();
}
// main.ts
import { functionMiddleware } from '@middlewares/function.middleware';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.use(functionMiddleware);
}
이렇게 사용하면 미들웨어를 모든 요청에 적용할 수 있습니다.
요청 파라미터의 검증이나 log를 찍는등의 다양한 역할을 미들웨어에서 처리할 수 있습니다.
Guard
guard를 전역, 컨트롤러, route에 적용하는 방법입니다.
적용할 guard를 작성해 줍니다.
// custom.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class CustomGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log('this is custom guard');
return true;
}
}
전체, 컨트롤러, 라우트 레벨에서 적용하는 예시 입니다.
// main.ts
// 모든 요청에 적용하는 법
app.useGlobalGuards(new CustomGuard())
// notifications.controller.ts
// 컨트롤러 레벨에서 적용하는 법
@Controller('notifications')
@UseGuards(CustomGuard)
export class NotificationsController {
@Get(':id')
// 라우트 레벨에서 적용하는 법
@UseGuards(CustomGuard)
findOne(@Param('id') id:string) {
// .....
}
}
이제 guard를 적절한 위치에서 사용할 수 있습니다.
토큰 검증이나 유저의 역할 검증등을 guard레벨에서 사용할 수 있습니다
Interceptor
interceptor 클래스를 생성해 줍시다
interceptor는 request lifecycle에서 컨트롤러를 지나기 전, 지나온 후에 방문하게 됩니다.pipe()
안에 메소드를 작성해주게 되면 컨트롤러를 지나온 후의 Response를 제어할 수 있습니다.
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CustomInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('this is pre custom interceptor');
return next
.handle()
.pipe(tap(() => console.log(`this is post custom route interceptor`)));
}
}
전체, 컨트롤러, 라우트 레벨에 적용하는 예시를 살펴 보겠습니다.
// main.ts
// 전체 레벨에서 적용하는 법
app.useGlobalInterceptors(new CustomInterceptor());
// notifications.controller.ts
// 컨트롤러 레벨에서 적용하는 법
@Controller('notifications')
@UseInterceptors(CustomInterceptor)
export class NotificationsController {
@Get(':id')
// 라우트 레벨에서 적용하는 법
@UseInterceptors(CustomInterceptor)
findOne(@Param('id') id:string) {
// .....
}
}
여기서 주의할 점은 요청이 들어올 시에는 global -> controller -> route 레벨로 interceptor를 방문하게 되지만
컨트롤러를 지나온 후 응답이 나갈시에는 route -> controller -> global 순으로 interceptor를 방문하게 됩니다
이제 interceptor를 적절한 위치에서 사용할 수 있습니다.
들어온 이미지를 resizing 하거나 로그를 찍는등의 다양한 상황에서 interceptor를 사용할 수 있습니다.
Pipe
pipe를 전역, 컨트롤러, route에 적용하는 방법입니다.
적용할 pipe를 작성해 줍니다.
// custom.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class CustomPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
console.log('this is custom pipe');
return value;
}
}
전체, 컨트롤러, 라우트 레벨에서 적용하는 예시 입니다.
// main.ts
// 모든 요청에 적용하는 법
app.useGlobalPipes(new CustomPipe());
// notifications.controller.ts
// 컨트롤러 레벨에서 적용하는 법
@Controller('notifications')
@UsePipes(CustomPipe)
export class NotificationsController {
@Get(':id')
// 라우트 레벨에서 적용하는 법
@UsePipes(CustomPipe)
findOne(@Param('id', ParseIntPipe) id:number) { // ParseIntPipe는 Nest에서 제공해주는 pipe 입니다.
// .....
}
}
이제 pipe를 적절한 위치에서 사용할 수 있습니다.
string parmeter를 int로 변경해준다거나 하는 등의 액션을 처리할 수 있습니다.
nest에서 제공해주는 유용한 pipe들이 많아서 사용하시면 개발에 도움이 됩니다.Nest Built-In Pipes
- pipe는 쿼리나 파라미터가 존재하지 않는 요청에서는 동작하지 않습니다
Exception Filter
exception filter는 17장에서 다룬 내용으로 대체하겠습니다.
exception filter가 발생했을시에 Request lifecycle이 어떻게 달라지는만 살펴보겠습니다.
코드에서 httpException을 발생 시켰고 그에 따른 request lifecycle이 달라진 모습입니다.
post-interceptor를 거치지 않고 바로 exception filter로 가는 모습을 확인할 수 있습니다.
요약하자면 아래의 단계와 같습니다.
- 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 exissts)
- Route interceptor(post-request)
- Controller interceptor(post-request)
- Global interceptor(post-request)
- Exception filters(route, then controller, then global)
- Server response
참조
'Node' 카테고리의 다른 글
algorithm-cli 생성기 (0) | 2022.02.07 |
---|---|
console.log vs process.stdout.write (0) | 2022.01.26 |
서버 기본 세팅 (0) | 2022.01.03 |
Package subpath './package.json' is not definded by "exports" in ~~~ (0) | 2021.11.14 |
About. Typeorm (0) | 2021.11.10 |