2022. 12. 26. 17:45γBackend/NestJS
μ΄μ κΈ
- 2022.12.22 - [Backend/NestJS] - NestJS Request Lifecycle (0) - κ°μ
- 2022.12.22 - [Backend/NestJS] - NestJS Request Lifecycle (1) - Middleware
- 2022.12.23 - [Backend/NestJS] - NestJS Request Lifecycle (2) - Guard
μΈν°μ ν°
NestJS λ΄μμ μΈν°μ ν°λ 컨νΈλ‘€λ¬ μ , νμμ λ€μν μν μ ν΄μ£Όλ μλͺ μ£ΌκΈ°λ₯Ό μλ―Ένλ€. 곡μλ¬Έμμμ μ€λͺ νλ μΈν°μ ν°κ° ꡬ체μ μΌλ‘ νλ μΌμ λ€μκ³Ό κ°λ€.
- λ©μλ(컨νΈλ‘€λ¬) μ€ν μ , νμ μΆκ°μ μΈ λ‘μ§ μν
- ν¨μμμ λ°νλ κ²°κ³Ό λ° μμΈμ λν λ³ν
- κΈ°λ³Έ ν¨μ λμμ νμ₯
- νΉμ 쑰건 νμμ μμ ν ν¨μμ μ¬μ μ(ex. μΊμ± λͺ©μ )
μΆμ²: NestJS 곡μ λ¬Έμ(https://docs.nestjs.com/interceptors)
μΈν°μ ν°μλ 2κ°μ μΈμκ° μ¬μ©λλ€. 첫λ²μ§Έλ μμ κ°λμμλ λ€λ£¬ μ€ν컨ν μ€νΈ(ExecutionContext)μ΄λ€. μ΄λ₯Ό μ΄μ©νμ¬ μΈν°μ ν°λ νΉμ 컨ν μ€νΈκ° μ΄λ€ νΈλ€λ¬λ₯Ό ν΅ν΄ μ²λ¦¬λλμ§ μ μ μλ€. λλ²μ§Έλ μ½νΈλ€λ¬(CallHandler)μ΄λ€. μΈν°μ ν°λ μ΄ μ½νΈλ€λ¬μ handle() λ©μλλ₯Ό ν΅ν΄ λ°νλλ RxJS Observable κ°μ²΄λ₯Ό λ°ννλλ‘ μμ±λμ΄μΌνλ€. κ°κ°μ μΈμλ₯Ό ν΅ν΄, μΈν°μ ν°λ νΈλ€λ¬ μ€ν μ , νμ μ κ·Όν μ μκ²λλ€.
μΈν°μ ν° μμ±
NestJSμ μΈν°μ ν°λ₯Ό μμ±νκΈ° μν΄μλ @Injectable() λ°μ½λ μ΄ν°λ₯Ό ν΅ν ν΄λμ€λ₯Ό μμ±ν΄μ£Όμ΄μΌνλ€. μ΄ λ ν΄λΉ ν΄λμ€λ NestJSμ NestInterceptorλ₯Ό μμν΄μΌνλ©°, ν΄λμ€ λ΄λΆμ μΌλ‘λ interceptor λ©μλλ₯Ό ν¬ν¨ν΄μΌνλ€. μ΄ λ μμ±λλ interceptor λ©μλμ μΈμλ‘, μμ μΈκΈν ExecutionContextμ CallHandlerκ° ν¬ν¨λλ€. κΈ°λ³Έμ μΌλ‘ μμ±λμ΄μΌ ν μ½λλ λ€μκ³Ό κ°λ€.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable } from "rxjs";
@Injectable()
export class TestInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
// νΈλ€λ¬ λλ¬ μ μΈν°μ
ν° λ‘μ§
return next.handle().pipe(
// νΈλ€λ¬ λλ¬ μ΄ν μΈν°μ
ν° λ‘μ§
)
}
}
μΈν°μ ν° μ μ©
NestJS νλ‘μ νΈμ μΈν°μ ν°λ₯Ό μ μ©νλ λ°©λ²μ ν¬κ² 3κ°μ§κ° μ‘΄μ¬νλ€. (κ°λμ λ°©μκ³Ό λμΌ)
- λͺ¨λ λ°μμ μλνλ μ μ μΈν°μ ν°
- λͺ¨λ λ΄μμ μλνλ μ μ μΈν°μ ν°
- λͺ¨λ λ΄ νΉμ 컨νΈλ‘€λ¬ νΉμ λ©μλμ μλνλ μΈν°μ ν°
μ΄λ€μ μλ μμλ μ‘°κΈ νΉμ΄νλ°, μ΄κΈ°μ μμν 1 -> 2 -> 3 μμκ° μλ 2 -> 1 -> 3 μμλ‘ μλνλ€! (νΈλ€λ¬λ₯Ό κ±°μΉ λ€μλ μμ)
λͺ¨λ λ°μμ μλνλ μ μ μΈν°μ ν°
μ΄λ₯Ό μν΄μλ NestJS μ ν리μΌμ΄μ μ useGlobalInterceptors() λ©μλλ₯Ό νμ©ν μ μλ€. μ΄ λ ν΄λΉ λ©μλμλ μΈν°μ ν° ν΄λμ€μ μΈμ€ν΄μ€κ° μ§μ μ λ¬λμ΄μΌνλ©°, λͺ¨λ λ°μμ μΆκ°ν΄μ£Όλ μΈν°μ ν°μ΄κΈ° λλ¬Έμ λ€λ₯Έ μμ‘΄μ±μ μ¬μ©ν μ μλ€.
import { NestFactory } from '@nestjs/core';
import { TestInterceptor } from './app.interceptor';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new TestInterceptor());
await app.listen(3000);
}
bootstrap();
λͺ¨λ λ΄μμ μλνλ μ μ μΈν°μ ν°
ν΄λΉ λ°©μμ ν΅νλ©΄ λ€λ₯Έ μμ‘΄μ±μ μ¬μ©νλ©΄μλ μΈν°μ ν°λ₯Ό μ μμΌλ‘ μ€μ ν μ μλ€.
곡μλ¬Έμμ λ°λ₯΄λ©΄, ν΄λΉ λ°©μμ λ€μμ νΉμ§μ κ°λλ€.
νΉμ μΈν°μ ν°μ μ μ μ€μ μ μν΄ ν΄λΉ λ°©μμΌλ‘ λͺ¨λμ μμ‘΄μ± μ£Όμ μ ν λ, μ£Όμ λλ λͺ¨λμ μμΉμ μκ΄μμ΄ μ΄λ μ μμΌλ‘ μλν©λλ€. μ¦, μΈν°μ ν°μ μ£Όμ μ ν΄λΉ μΈν°μ ν°κ° μμ±λ λͺ¨λμμ μ΄λ£¨μ΄μ Έλ 무방ν©λλ€.
μ΄μ λ°λ₯΄λ©΄ μ μ μΈν°μ ν° μ€μ μ μν μμ‘΄μ± μ£Όμ μ κΌ μ΅μμ λͺ¨λμ μ΄λ£¨μ΄μ§μ§ μμλ λλ€λ κ²μ μλ―Ένλ€.
μ΄λ₯Ό νμΈνκΈ° μν΄ μ΅μμ λͺ¨λμ΄ μλ TestModuleμ μμ±ν΄μ μμ‘΄μ± μ£Όμ μ ν΄λ³΄κ² λ€.
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TestInterceptor } from './app.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: TestInterceptor,
},
],
})
export class TestModule {}
ν΄λΉ λͺ¨λμ μ΅μμ λͺ¨λ(app.module.ts)μ μ£Όμ ν TestModuleκ³Ό 무κ΄ν 컨νΈλ‘€λ¬λ₯Ό νΈμΆν΄λ³΄λ μ μμΌλ‘ μ μλνλ€!
λͺ¨λ λ΄ νΉμ 컨νΈλ‘€λ¬ νΉμ λ©μλμ μλνλ μΈν°μ ν°
ν΄λΉ λ°©μμ ν΅νλ©΄, νΉμ 컨νΈλ‘€λ¬ νΉμ λ©μλμλ§ μ μ©λλ μΈν°μ ν°λ₯Ό μΆκ°ν μ μλ€. NestJSμ @UseInterceptors() λ°μ½λ μ΄ν°λ₯Ό ν΅ν΄ μΆκ°νλ λ°©μμ΄κ³ , λ©μλμ μΈμλ‘λ μΈν°μ ν° ν΄λμ€ νΉμ ν΄λμ€λ₯Ό ν΅ν μΈμ€ν΄μ€κ° μꡬλλ€.
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { AppService } from './app.service';
import { TestInterceptor } from './interceptors/test2.interceptor';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('test')
@UseInterceptors(TestInterceptor) // μΈν°μ
ν°λ₯Ό μΆκ°νλ λ°©μ
getHello(): string {
return this.appService.getHello();
}
}
μΈν°μ ν° νμ©
λ€μμ NestJSμμ μΈν°μ ν°λ₯Ό νμ©νλ λ°©μλ€μ λν΄ μμλ³΄κ² λ€. μμλ€μ 곡μλ¬Έμμ μμ λ₯Ό κ°μ Έμ¨ κ²λ μκ³ , μ€μ νλ‘μ νΈμ νμ©νλ μ½λλ₯Ό κ°μ Έμ¨ λΆλΆλ μ‘΄μ¬νλ€.
νΈλ€λ¬ μ²λ¦¬ μκ° λ‘κ·Έ μΆλ ₯ μμ
μ¬κΈ°μμλ NestJS μΈν°μ ν°μ RxJSμ tap Operatorλ₯Ό μ΄μ©νμ¬ νΈλ€λ¬λ₯Ό μ²λ¦¬νλλ° μΌλ§λ 걸리λμ§ λ‘κ·Έλ₯Ό μΆλ ₯νλ μμ λ₯Ό μμ±νλ€. μ½λλ λ€μκ³Ό κ°λ€.
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class TestInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
const timer = Date.now();
console.log('νΈλ€λ¬ λμ°© μ ...');
return next
.handle()
.pipe(tap(() => console.log(`νΈλ€λ¬ λμ°© ν...${Date.now() - timer}ms`)));
}
}
λ°ν νν ν νλ¦Ώ μ μ©
λ€μμ ν΄λΌμ΄μΈνΈμ μμ²μ λν μλ΅μ μΌμ ν λ°μ΄ν° ꡬ쑰λ₯Ό ν΅ν΄ λ°ννκΈ° μν μΈν°μ ν° μμ μ΄λ€.
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
// μ¬μ©μμκ² λ°νν λ°μ΄ν° ν
νλ¦Ώ
interface ResponseTemplate<T> {
message: string;
data: T;
}
@Injectable()
export class TestInterceptor<T>
implements NestInterceptor<T, ResponseTemplate<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
):
| Observable<ResponseTemplate<T>>
| Promise<Observable<ResponseTemplate<T>>> {
return next.handle().pipe(
map((resData) => ({
message: resData.message || '',
data: resData.data || '',
})),
);
}
}
μμΈ μ²λ¦¬
λ€μμ μλ¬κ° λ°μνμ λ μ΄λ₯Ό 무μνκ³ μΌκ΄μ μΌλ‘ NestJS BadRequest μ²λ¦¬λ₯Ό ν΄μ£Όλ μΈν°μ ν°μ΄λ€.
import {
BadRequestException,
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class TestInterceptor<T>
implements NestInterceptor<T, ResponseTemplate<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
):
| Observable<ResponseTemplate<T>>
| Promise<Observable<ResponseTemplate<T>>> {
return next
.handle()
// μλ¬ λ°μ μ μΌκ΄μ μΌλ‘ NestJS BadRequestException μ²λ¦¬
.pipe(catchError((err) => throwError(() => new BadRequestException())));
}
}
μμ² μκ° μ ν μΈν°μ ν°
λ€μ μμ λ, μμ² λ°νκΉμ§ 걸리λ μκ°μ΄ 5μ΄κ° λλ κ²½μ°, μΌκ΄μ μΌλ‘ Timeout μ²λ¦¬λ₯Ό μ§ννλ μμ μ΄λ€.
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), // 5000msμ λκΈ° ν TimeoutErrorλ₯Ό λμ§
catchError((err) => {
if (err instanceof TimeoutError) { // λ§μ½ μλ¬κ° TimeoutErrorλΌλ©΄ RequestTimeoutExceptionμ λμ§
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}
μΊμ± μΈν°μ ν°
λ§μ§λ§μ μμ²μ΄ λ©μΈ λΉμ¦λμ€ λ‘μ§μμ μ²λ¦¬λκΈ° μ , μΊμ± λ°μ΄ν°κ° μλ€λ©΄ 곧λ°λ‘ μμ²μ μ²λ¦¬νλ μΈν°μ ν°μ΄λ€.
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();
}
}
μ μ½λλ λ¨μ μμμ λΆκ³Όνλ©°, μ€μ μ½λμμλ isCached λΆλΆμ μ€μ μΊμ± μ¬λΆλ₯Ό νμΈνλ λ‘μ§μ΄ ν¬ν¨λμ΄μΌ νλ€. λ§μ½ μΊμ±μ΄ λμ΄μλ κ²½μ° of()λ₯Ό ν΅ν΄ μμ±λ μλ‘μ΄ μ€νΈλ¦Όμ 곧λ°λ‘ ν΄λΌμ΄μΈνΈμ λ°ννκ³ , 컨νΈλ‘€λ¬ λ‘μ§μ μ²λ¦¬λμ§ μλλ€.
'Backend > NestJS' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
NestJS Request Lifecycle (5) - Exception Filter (0) | 2023.01.13 |
---|---|
NestJS Request Lifecycle (4) - Pipe (0) | 2022.12.29 |
NestJS Request Lifecycle (2) - Guard (0) | 2022.12.23 |
NestJS Request Lifecycle (1) - Middleware (0) | 2022.12.22 |
NestJS Request Lifecycle (0) - κ°μ (0) | 2022.12.22 |