๐Ÿ“‘ ๊ณต์‹ ๋ฌธ์„œ ๋ฒˆ์—ญ/Nest.js

[Nest.js] Overview - Interceptors, Custom decorators

dev_writer 2025. 7. 13. 17:40

์ธํ„ฐ์…‰ํ„ฐ

์ธํ„ฐ์…‰ํ„ฐ (interceptor)๋Š” @Injectable() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ๋ถ™์€ ํด๋ž˜์Šค์ด๋ฉฐ, NestInterceptor ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

 

์ธํ„ฐ์…‰ํ„ฐ๋Š” ๊ด€์  ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ (AOP) ๊ธฐ๋ฒ•์—์„œ ์˜๊ฐ์„ ๋ฐ›์€ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

  • ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ „/ํ›„์— ์ถ”๊ฐ€ ๋กœ์ง์„ ๋ฐ”์ธ๋”ฉ
  • ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ณ€ํ™˜
  • ํ•จ์ˆ˜์—์„œ ๋˜์ง„ ์˜ˆ์™ธ๋ฅผ ๋ณ€ํ™˜
  • ๊ธฐ๋ณธ ํ•จ์ˆ˜ ๋™์ž‘์„ ํ™•์žฅ
  • ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ์™„์ „ํžˆ ๋Œ€์ฒด (์˜ˆ: ์บ์‹ฑ ๋ชฉ์ )

๊ธฐ๋ณธ ์‚ฌํ•ญ

๊ฐ ์ธํ„ฐ์…‰ํ„ฐ๋Š” intercept() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉฐ, ์ด ๋ฉ”์„œ๋“œ๋Š” ๋‘ ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋Š” ExecutionContext ์ธ์Šคํ„ด์Šค์ด๋ฉฐ, ์ด๋Š” ๊ฐ€๋“œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ExecutionContext๋Š” ArgumentsHost๋ฅผ ์ƒ์†๋ฐ›์Šต๋‹ˆ๋‹ค. ArgumentsHost๋Š” ์•ž์„œ ์˜ˆ์™ธ ํ•„ํ„ฐ ์ฑ•ํ„ฐ์—์„œ ์‚ดํŽด๋ณธ ๋ฐ”์™€ ๊ฐ™์ด, ์›๋ž˜ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋œ ์ธ์ž๋“ค์„ ๊ฐ์‹ธ๋Š” ๋ž˜ํผ ๊ฐ์ฒด์ด๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์œ ํ˜•์— ๋”ฐ๋ผ ๋‹ค์–‘ํ•œ ์ธ์ž ๋ฐฐ์—ด์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ์ด ์ฃผ์ œ์— ๋Œ€ํ•ด์„œ๋Š” ์˜ˆ์™ธ ํ•„ํ„ฐ ์„น์…˜์„ ๋‹ค์‹œ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹คํ–‰ ์ปจํ…์ŠคํŠธ

ExecutionContext๋Š” ArgumentsHost๋ฅผ ์ƒ์†ํ•จ์œผ๋กœ์จ ํ˜„์žฌ ์‹คํ–‰ ๊ณผ์ •์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ณด์กฐ ๋ฉ”์„œ๋“œ๋“ค๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ •๋ณด๋“ค์€ ๋ณด๋‹ค ์ผ๋ฐ˜์ ์ธ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ๊ตฌ์ถ•ํ•  ๋•Œ ์œ ์šฉํ•˜๋ฉฐ, ๋‹ค์–‘ํ•œ ์ปจํŠธ๋กค๋Ÿฌ, ๋ฉ”์„œ๋“œ, ์‹คํ–‰ ์ปจํ…์ŠคํŠธ์—์„œ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ExecutionContext์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๋ฉด ์—ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

ํ•ธ๋“ค๋Ÿฌ ํ˜ธ์ถœ

๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” CallHandler์ž…๋‹ˆ๋‹ค. CallHandler ์ธํ„ฐํŽ˜์ด์Šค๋Š” handle() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ„ฐ์…‰ํ„ฐ ๋‚ด๋ถ€์—์„œ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ intercept() ๋ฉ”์„œ๋“œ ๊ตฌํ˜„ ๋‚ด์—์„œ handle() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฉด, ํ•ด๋‹น ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ๋Š” ์ „ํ˜€ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ ๋•๋ถ„์— intercept() ๋ฉ”์„œ๋“œ๋Š” ์š”์ฒญ/์‘๋‹ต ์ŠคํŠธ๋ฆผ์„ ํšจ๊ณผ์ ์œผ๋กœ ๊ฐ์‹ธ๋Š” (wrapper) ์—ญํ• ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ, ์ตœ์ข… ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „๊ณผ ํ›„ ๋ชจ๋‘์— ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. handle() ํ˜ธ์ถœ ์ „์— ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋ถ„๋ช…ํ•˜์ง€๋งŒ, ๊ทธ ์ดํ›„์— ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”? ์ด๋Š” handle()์ด Observable์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, RxJS์˜ ๊ฐ•๋ ฅํ•œ ์—ฐ์‚ฐ์ž๋“ค์„ ํ™œ์šฉํ•˜์—ฌ ์‘๋‹ต์„ ์ถ”๊ฐ€๋กœ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. AOP (๊ด€์  ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ) ์šฉ์–ด๋กœ, ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์‹œ์  (์ฆ‰, handle() ํ˜ธ์ถœ)์€ ํฌ์ธํŠธ์ปท (Pointcut)์ด๋ผ๊ณ  ํ•˜๋ฉฐ, ์ด๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€ ๋กœ์ง์„ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ์ง€์ ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, POST /cats ์š”์ฒญ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค. ์ด ์š”์ฒญ์€ CatsController์— ์ •์˜๋œ create() ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋„์ค‘์— handle()์„ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š” ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ์‹คํ–‰๋˜๋ฉด, create() ๋ฉ”์„œ๋“œ๋Š” ์ „ํ˜€ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ handle()์ด ํ˜ธ์ถœ๋˜๊ณ  ๊ทธ Observable์ด ๋ฐ˜ํ™˜๋˜๋ฉด, create() ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์‹คํ–‰๋˜๋ฉฐ, ๊ทธ ์‘๋‹ต ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ ํ›„ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ด€์  ๊ฐ€๋กœ์ฑ„๊ธฐ (Aspect interception)

์šฐ๋ฆฌ๊ฐ€ ์‚ดํŽด๋ณผ ์ฒซ ๋ฒˆ์งธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ์„ ๋กœ๊น…ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค (์˜ˆ: ์‚ฌ์šฉ์ž ํ˜ธ์ถœ ์ €์žฅ, ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๋””์ŠคํŒจ์น˜, ํƒ€์ž„์Šคํƒฌํ”„ ๊ณ„์‚ฐ ๋“ฑ). ์•„๋ž˜๋Š” ๊ฐ„๋‹จํ•œ LoggingInterceptor ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

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`)),
      );
  }
}
ํžŒํŠธ
NestInterceptor<T, R>๋Š” ์ œ๋„ค๋ฆญ ์ธํ„ฐํŽ˜์ด์Šค๋กœ, ์—ฌ๊ธฐ์„œ T๋Š” Observable<T>์˜ ํƒ€์ž… (์‘๋‹ต ์ŠคํŠธ๋ฆผ์„ ์ง€์›ํ•จ)์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, R์€ Observable<R>์— ๊ฐ์‹ธ์ง„ ๊ฐ’์˜ ํƒ€์ž…์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
์•Œ๋ฆผ
์ธํ„ฐ์…‰ํ„ฐ๋Š” ์ปจํŠธ๋กค๋Ÿฌ, ํ”„๋กœ๋ฐ”์ด๋”, ๊ฐ€๋“œ ๋“ฑ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

handle()์€ RxJS Observable์„ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ์šฐ๋ฆฌ๋Š” ์ŠคํŠธ๋ฆผ์„ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์ œ์—์„œ๋Š” tap() ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, ์ด ์—ฐ์‚ฐ์ž๋Š” Observable ์ŠคํŠธ๋ฆผ์ด ์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ์ ์œผ๋กœ ์ข…๋ฃŒ๋  ๋•Œ ์ต๋ช… ๋กœ๊น… ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์ง€๋งŒ, ๊ทธ ์™ธ์—๋Š” ์‘๋‹ต ์‚ฌ์ดํด์— ๊ฐ„์„ญํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ธํ„ฐ์…‰ํ„ฐ ์—ฐ๊ฒฐํ•˜๊ธฐ

์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜จ @UseInterceptors() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ดํ”„๋‚˜ ๊ฐ€๋“œ์ฒ˜๋Ÿผ ์ธํ„ฐ์…‰ํ„ฐ๋„ ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ”์œ„, ๋ฉ”์„œ๋“œ ๋ฒ”์œ„, ๋˜๋Š” ์ „์—ญ ๋ฒ”์œ„๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@UseInterceptors(LoggingInterceptor)
export class CatsController {}
ํžŒํŠธ
@UseInterceptors() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

 

์œ„์™€ ๊ฐ™์€ ๊ตฌ์„ฑ ๋ฐฉ์‹์œผ๋กœ ์„ค์ •ํ•˜๋ฉด, CatsController์— ์ •์˜๋œ ๊ฐ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” LoggingInterceptors๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋ˆ„๊ตฐ๊ฐ€ GET /cats ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ํ‘œ์ค€ ์ถœ๋ ฅ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ถœ๋ ฅ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค:

Before...
After... 1ms

 

์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” LoggingInterceptor ํด๋ž˜์Šค๋ฅผ ์ „๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค (์ธ์Šคํ„ด์Šค๊ฐ€ ์•„๋‹Œ). ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ธ์Šคํ„ด์Šคํ™” ์ฑ…์ž„์„ ํ”„๋ ˆ์ž„์›Œํฌ์— ์œ„์ž„ํ•˜๊ฒŒ ๋˜์–ด ์˜์กด์„ฑ ์ฃผ์ž…์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ํŒŒ์ดํ”„ (pipes), ๊ฐ€๋“œ (guards), ์˜ˆ์™ธ ํ•„ํ„ฐ (exception filters)์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์ธ๋ผ์ธ (in-place) ์ธ์Šคํ„ด์Šค๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@UseInterceptors(new LoggingInterceptor())
export class CatsController {}

 

์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ์œ„์˜ ๊ตฌ์„ฑ์€ ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์— ์„ ์–ธ๋œ ๋ชจ๋“  ํ•ธ๋“ค๋Ÿฌ์— ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ธํ„ฐ์…‰ํ„ฐ์˜ ๋ฒ”์œ„๋ฅผ ๋‹จ์ผ ๋ฉ”์„œ๋“œ๋กœ ์ œํ•œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ๋‹จ์ˆœํžˆ ํ•ด๋‹น ๋ฉ”์„œ๋“œ์— ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์ „์—ญ ์ธํ„ฐ์…‰ํ„ฐ (global interceptor)๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด, Nest ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค์˜ useGlobalInterceptors() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

 

์ „์—ญ ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด, ์ฆ‰ ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋ชจ๋“  ๋ผ์šฐํŠธ์— ๊ฑธ์ณ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ์ฃผ์ž… ์ธก๋ฉด์—์„œ ๋ณด๋ฉด, ์œ„์˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ useGlobalInterceptors()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“ˆ ์™ธ๋ถ€์—์„œ ๋“ฑ๋ก๋œ ์ „์—ญ ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ•ด๋‹น ๋ฐ”์ธ๋”ฉ์ด ์–ด๋–ค ๋ชจ๋“ˆ์˜ ์ปจํ…์ŠคํŠธ ๋ฐ–์—์„œ ์ˆ˜ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ ๋ฐฉ์‹์œผ๋กœ ์–ด๋–ค ๋ชจ๋“ˆ ๋‚ด๋ถ€์—์„œ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ง์ ‘ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
  ],
})
export class AppModule {}
ํžŒํŠธ
์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ„ฐ์…‰ํ„ฐ์— ์˜์กด์„ฑ ์ฃผ์ž…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ, ์ด ๊ตฌ์„ฑ์ด ์‚ฌ์šฉ๋œ ๋ชจ๋“ˆ์ด ์–ด๋””๋“ ์ง€ ๊ฐ„์— ํ•ด๋‹น ์ธํ„ฐ์…‰ํ„ฐ๋Š” ์‹ค์ œ๋กœ ์ „์—ญ (Global)์œผ๋กœ ์ ์šฉ๋œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ์ธํ„ฐ์…‰ํ„ฐ (์œ„ ์˜ˆ์‹œ์—์„œ๋Š” LoggingInterceptor)๊ฐ€ ์ •์˜๋œ ๋ชจ๋“ˆ์—์„œ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ useClass๋งŒ์ด ์ปค์Šคํ…€ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ๋“ฑ๋กํ•˜๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค. ๋” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

์‘๋‹ต ๋งคํ•‘

handle() ๋ฉ”์„œ๋“œ๋Š” Observable์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์ŠคํŠธ๋ฆผ์€ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๊ฐ’์„ ํฌํ•จํ•˜๋ฏ€๋กœ, RxJS์˜ map() ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‰ฝ๊ฒŒ ๋ณ€ํ˜•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฃผ์˜
์‘๋‹ต ๋งคํ•‘ ๊ธฐ๋Šฅ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ณ ์œ ์˜ ์‘๋‹ต ์ „๋žต๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (@Res() ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ธˆ์ง€๋ฉ๋‹ˆ๋‹ค.)

 

TransformInterceptor๋ฅผ ์ƒ์„ฑํ•ด ๋ด…์‹œ๋‹ค. ์ด ์ธํ„ฐ์…‰ํ„ฐ๋Š” ๊ฐ ์‘๋‹ต์„ ๋‹จ์ˆœํ•˜๊ฒŒ ์ˆ˜์ •ํ•˜์—ฌ ๋™์ž‘ ๊ณผ์ •์„ ๋ณด์—ฌ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. RxJS์˜ map() ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๊ฐ์ฒด์˜ data ์†์„ฑ์— ํ• ๋‹นํ•˜๊ณ , ์ด ์ƒˆ ๊ฐ์ฒด๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

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 })));
  }
}
ํžŒํŠธ
Nest์˜ ์ธํ„ฐ์…‰ํ„ฐ๋Š” intercept() ๋ฉ”์„œ๋“œ๊ฐ€ ๋™๊ธฐ์‹์ด๋“  ๋น„๋™๊ธฐ์‹์ด๋“  ๋ชจ๋‘ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ฉ”์„œ๋“œ๋ฅผ async๋กœ ๊ฐ„๋‹จํžˆ ๋ณ€๊ฒฝํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑํ•˜๋ฉด, ๋ˆ„๊ตฐ๊ฐ€ GET /cats ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ์˜ ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค (ํ•ด๋‹น ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋นˆ ๋ฐฐ์—ด []์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ๊ฒฝ์šฐ):

{
  "data": []
}

 

์ธํ„ฐ์…‰ํ„ฐ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์š”๊ตฌ ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์†”๋ฃจ์…˜์„ ๋งŒ๋“œ๋Š” ๋ฐ ํฐ ๊ฐ€์น˜๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ชจ๋“  null ๊ฐ’์„ ๋นˆ ๋ฌธ์ž์—ด ''๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค. ์ด ์ž‘์—…์€ ๋‹จ ํ•œ ์ค„์˜ ์ฝ”๋“œ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ „์—ญ์œผ๋กœ ๋ฐ”์ธ๋”ฉํ•˜๋ฉด ๋“ฑ๋ก๋œ ๋ชจ๋“  ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ž๋™์œผ๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

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

@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next
      .handle()
      .pipe(map(value => value === null ? '' : value ));
  }
}

์˜ˆ์™ธ ๋งคํ•‘

๋˜ ๋‹ค๋ฅธ ํฅ๋ฏธ๋กœ์šด ์‚ฌ์šฉ ์‚ฌ๋ก€๋Š” RxJS์˜ catchError() ์—ฐ์‚ฐ์ž๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ (์žฌ์ •์˜)ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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())),
      );
  }
}

์ŠคํŠธ๋ฆผ ์žฌ์ •์˜

๊ฒฝ์šฐ์— ๋”ฐ๋ผ ํ•ธ๋“ค๋Ÿฌ์˜ ํ˜ธ์ถœ ์ž์ฒด๋ฅผ ์™„์ „ํžˆ ๋ง‰๊ณ , ๋Œ€์‹  ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๋Œ€ํ‘œ์ ์ธ ์˜ˆ๋กœ๋Š” ์‘๋‹ต ์‹œ๊ฐ„์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ์บ์‹œ (cache) ๊ตฌํ˜„์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์บ์‹œ์—์„œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์บ์‹œ ์ธํ„ฐ์…‰ํ„ฐ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์ƒํ™ฉ์—์„œ๋Š” TTL, ์บ์‹œ ๋ฌดํšจํ™”, ์บ์‹œ ํฌ๊ธฐ ๋“ฑ์˜ ์š”์†Œ๋„ ๊ณ ๋ คํ•ด์•ผ ํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ๊ฐœ๋…์„ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์ œ๋งŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

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();
  }
}

 

CacheInterceptor๋Š” ํ˜„์žฌ isCached ๋ณ€์ˆ˜์™€ ์‘๋‹ต [] ๋ชจ๋‘ ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์ ์€ RxJS์˜ of() ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ŠคํŠธ๋ฆผ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ „ํ˜€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. CacheInterceptor๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๋นˆ ๋ฐฐ์—ด ์‘๋‹ต์ด ์ฆ‰์‹œ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ๋ณด๋‹ค ๋ฒ”์šฉ์ ์ธ ์†”๋ฃจ์…˜์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” Reflector๋ฅผ ํ™œ์šฉํ•˜๊ณ  ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Reflector์— ๋Œ€ํ•œ ์„ค๋ช…์€ Guards ์ฑ•ํ„ฐ์—์„œ ์ž˜ ๋‹ค๋ค„์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์—ฐ์‚ฐ์ž๋“ค

RxJS ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ ํ•˜๋‚˜์˜ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํŠธ ์š”์ฒญ์— ๋Œ€ํ•ด ํƒ€์ž„์•„์›ƒ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋„๋ก ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์•„๋ฌด๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด, ์—๋Ÿฌ ์‘๋‹ต์œผ๋กœ ์š”์ฒญ์„ ์ข…๋ฃŒํ•˜๊ณ ์ž ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ์œผ๋กœ ์ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

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);
      }),
    );
  };
};

 

5์ดˆ๊ฐ€ ์ง€๋‚˜๋ฉด ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ RequestTimeoutException์„ ๋˜์ง€๊ธฐ ์ „์— ์‚ฌ์šฉ์ž ์ •์˜ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•˜์—ฌ (์˜ˆ๋ฅผ ๋“ค์–ด) ์ž์›์„ ํ•ด์ œํ•˜๋Š” ๋“ฑ์˜ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.


์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ

Nest๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ผ๋Š” ์–ธ์–ด ๊ธฐ๋Šฅ์„ ์ค‘์‹ฌ์œผ๋กœ ๊ตฌ์ถ•๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ๋งŽ์€ ์ฃผ์š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ ์ž˜ ์•Œ๋ ค์ง„ ๊ฐœ๋…์ด์ง€๋งŒ, JavaScript ์„ธ๊ณ„์—์„œ๋Š” ๋น„๊ต์  ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ๋” ์ž˜ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ณด๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์ •์˜์ž…๋‹ˆ๋‹ค:

ES2016 ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ‘œํ˜„์‹์œผ๋กœ, ๋Œ€์ƒ (target), ์ด๋ฆ„ (name), ์†์„ฑ ์„ค๋ช…์ž (property descriptor)๋ฅผ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” @ ๊ธฐํ˜ธ๋ฅผ ์•ž์— ๋ถ™์—ฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ ์šฉํ•  ๋Œ€์ƒ์„ ๊ฐ€์žฅ ์œ„์— ์œ„์น˜์‹œ์ผœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํด๋ž˜์Šค, ๋ฉ”์„œ๋“œ, ์†์„ฑ ์ค‘ ์–ด๋А ๊ณณ์—๋„ ์ •์˜๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ

Nest๋Š” HTTP ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์šฉํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์ œ๊ณต๋˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ชฉ๋ก๊ณผ ๊ทธ๊ฒƒ์ด ๋‚˜ํƒ€๋‚ด๋Š” ์ˆœ์ˆ˜ Express (๋˜๋Š” Fastify) ๊ฐ์ฒด๋“ค์ž…๋‹ˆ๋‹ค.

@Request(), @Req() req
@Response(), @Res() res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(key?: string) req.headers / req.headers[name]
@Ip() req.ip
@HostParam() req.hosts

 

๋˜ํ•œ, ์ž์‹ ๋งŒ์˜ ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์™œ ์œ ์šฉํ• ๊นŒ์š”?

 

Node.js ์„ธ๊ณ„์—์„œ๋Š” ์š”์ฒญ ๊ฐ์ฒด์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ธ ๊ด€๋ก€์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๊ณ  ๋‚˜์„œ ๊ฐ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ์ด๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

const user = req.user;

 

์ฝ”๋“œ๋ฅผ ๋” ์ฝ๊ธฐ ์‰ฝ๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด @User() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

 

๊ทธ๋Ÿฐ ๋‹ค์Œ, ํ•„์š”์— ๋”ฐ๋ผ ์–ธ์ œ๋“ ์ง€ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}

๋ฐ์ดํ„ฐ ์ „๋‹ฌ

๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ ๋™์ž‘์ด ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ ธ์•ผ ํ•  ๊ฒฝ์šฐ, data ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜์— ์ธ์ž๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ ๊ฐ€์ง€ ์‚ฌ์šฉ ์˜ˆ๋กœ๋Š”, ์š”์ฒญ ๊ฐ์ฒด์—์„œ ํ‚ค๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์†์„ฑ์„ ์ถ”์ถœํ•˜๋Š” ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ธ์ฆ ๊ณ„์ฒญ์ด ์š”์ฒญ์„ ๊ฒ€์ฆํ•˜๊ณ  ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ๋ฅผ ์š”์ฒญ ๊ฐ์ฒด์— ์ถ”๊ฐ€ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ์ธ์ฆ๋œ ์š”์ฒญ์˜ ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

{
  "id": 101,
  "firstName": "Alan",
  "lastName": "Turing",
  "email": "alan@email.com",
  "roles": ["admin"]
}

 

์†์„ฑ ์ด๋ฆ„์„ ํ‚ค๋กœ ๋ฐ›์•„ ํ•ด๋‹น ๊ฐ’์ด ์กด์žฌํ•˜๋ฉด ๋ฐ˜ํ™˜ํ•˜๊ณ , ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ user ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” undefined๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ •์˜ํ•ด ๋ด…์‹œ๋‹ค.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    return data ? user?.[data] : user;
  },
);

 

๋‹ค์Œ์€ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @User() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ํŠน์ • ์†์„ฑ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค:

@Get()
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`);
}

 

์ด ๋™์ผํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋‹ค๋ฅธ ํ‚ค์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋กœ ๋‹ค๋ฅธ ์†์„ฑ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. user ๊ฐ์ฒด๊ฐ€ ๊นŠ๊ฑฐ๋‚˜ ๋ณต์žกํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ, ์ด๋ฅผ ํ†ตํ•ด ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„์„ ๋” ์‰ฝ๊ณ  ์ฝ๊ธฐ ์ข‹๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํžŒํŠธ
TypeScript ์‚ฌ์šฉ์ž๋ผ๋ฉด createParamDecorator<T>()๋Š” ์ œ๋„ค๋ฆญ์ด๋ผ๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด createParamDecorator<string>((data, ctx) => ...)์™€ ๊ฐ™์ด ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ createParamDecorator((data: string, ctx) => ...)์ฒ˜๋Ÿผ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์„ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์ƒ๋žตํ•˜๋ฉด data์˜ ํƒ€์ž…์€ any๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

ํŒŒ์ดํ”„์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ

Nest๋Š” ์ปค์Šคํ…€ ๋งค๊ฐœ๋ณ€์ˆ˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๊ธฐ๋ณธ ์ œ๊ณต ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋“ค (@Body(), @Param(), @Query())๊ณผ ๋™์ผํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์ฃผ์„ ์ฒ˜๋ฆฌ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ์—๋„ ํŒŒ์ดํ”„๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค (์˜ˆ์ œ์—์„œ๋Š” user ์ธ์ž). ๋˜ํ•œ ํŒŒ์ดํ”„๋ฅผ ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ์ง์ ‘ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

@Get()
async findOne(
  @User(new ValidationPipe({ validateCustomDecorators: true }))
  user: UserEntity,
) {
  console.log(user);
}
ํžŒํŠธ
validateCustomDecorators ์˜ต์…˜์ด true๋กœ ์„ค์ •๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ValidationPipe๋Š” ์ปค์Šคํ…€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์ฃผ์„ ์ฒ˜๋ฆฌ๋œ ์ธ์ž๋ฅผ ๊ฒ€์ฆํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํ•ฉ์„ฑ

Nest๋Š” ์—ฌ๋Ÿฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜๋กœ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ํ—ฌํผ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ธ์ฆ๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ํ†ตํ•ฉํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์„ฑ์œผ๋กœ ์ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { applyDecorators } from '@nestjs/common';

export function Auth(...roles: Role[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  );
}

 

๊ทธ๋Ÿฐ ๋‹ค์Œ, ์ด ์ปค์Šคํ…€ @Auth() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@Get('users')
@Auth('admin')
findAllUsers() {}

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋„ค ๊ฐœ์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ํ•˜๋‚˜์˜ ์„ ์–ธ์œผ๋กœ ๋ชจ๋‘ ์ ์šฉ๋˜๋Š” ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฝ๊ณ 
@nestjs/swagger ํŒจํ‚ค์ง€์˜ @ApiHideProperty() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํ•ฉ์„ฑ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉฐ, applyDecorators ํ•จ์ˆ˜์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Reference