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

[Nest.js] Overview - Middleware, Exception filters

dev_writer 2025. 7. 7. 21:23

๋ฏธ๋“ค์›จ์–ด

๋ฏธ๋“ค์›จ์–ด (Middleware)๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋Š” ์š”์ฒญ (request) ๊ฐ์ฒด, ์‘๋‹ต (response) ๊ฐ์ฒด, ๊ทธ๋ฆฌ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์š”์ฒญ-์‘๋‹ต ์‚ฌ์ดํด ๋‚ด์—์„œ ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜ (next)์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด next ํ•จ์ˆ˜๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ next๋ผ๋Š” ์ด๋ฆ„์˜ ๋ณ€์ˆ˜๋กœ ํ‘œํ˜„๋ฉ๋‹ˆ๋‹ค.

 

Nest ๋ฏธ๋“ค์›จ์–ด๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Express ๋ฏธ๋“ค์›จ์–ด์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๊ณต์‹ Express ๋ฌธ์„œ์—์„œ ์„ค๋ช…ํ•œ ๋ฏธ๋“ค์›จ์–ด์˜ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค:

๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
- ์–ด๋–ค ์ฝ”๋“œ๋“  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
- ์š”์ฒญ (request) ๋ฐ ์‘๋‹ต (response) ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
- ์š”์ฒญ-์‘๋‹ต ์‚ฌ์ดํด์„ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ๋‹ค.
- ์Šคํƒ ๋‚ด ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
- ๋งŒ์•ฝ ํ˜„์žฌ ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๊ฐ€ ์š”์ฒญ-์‘๋‹ต ์‚ฌ์ดํด์„ ์ข…๋ฃŒํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, next()๋ฅผ ๋ฐ˜๋“œ์‹œ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์ฑ„ ๋ฉˆ์ถฐ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

Nest์—์„œ ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด๋Š” ํ•จ์ˆ˜ ํ˜•ํƒœ ๋˜๋Š” @Injectable() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ๋ถ™์€ ํด๋ž˜์Šค ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ž˜์Šค ๋ฐฉ์‹์—์„œ๋Š” NestMiddleware ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜ ๋ฐฉ์‹์€ ํŠน๋ณ„ํ•œ ์š”๊ตฌ์‚ฌํ•ญ ์—†์ด ์ผ๋ฐ˜ ํ•จ์ˆ˜๋กœ ์ •์˜ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์šฐ์„ , ํด๋ž˜์Šค ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ„๋‹จํ•œ ๋ฏธ๋“ค์›จ์–ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฒฝ๊ณ 
Express์™€ Fastify๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด ๋‹ค๋ฅด๋ฉฐ, ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ๋˜ํ•œ ์„œ๋กœ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

์˜์กด์„ฑ ์ฃผ์ž…

Nest์˜ ๋ฏธ๋“ค์›จ์–ด๋Š” ์˜์กด์„ฑ ์ฃผ์ž… (Dependendy Injection)์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋ฐ”์ด๋”๋‚˜ ์ปจํŠธ๋กค๋Ÿฌ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋ฏธ๋“ค์›จ์–ด๋„ ๊ฐ™์€ ๋ชจ๋“ˆ ๋‚ด์— ๋“ฑ๋ก๋œ ์˜์กด์„ฑ๋“ค์„ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์—ญ์‹œ ์ผ๋ฐ˜์ ์œผ๋กœ ์ƒ์„ฑ์ž (constructor)๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

๋ฏธ๋“ค์›จ์–ด ์ ์šฉ

@Module() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์•ˆ์—๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์„ค์ •ํ•˜๋Š” ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค. ๋Œ€์‹ , ๋ฏธ๋“ค์›จ์–ด๋Š” ๋ชจ๋“ˆ ํด๋ž˜์Šค์˜ configure() ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํฌํ•จํ•˜๋Š” ๋ชจ๋“ˆ์€ NestModule ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋ฉฐ, ์—ฌ๊ธฐ์„œ๋Š” AppModule์—์„œ LoggerMiddleware๋ฅผ ์„ค์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

 

์œ„ ์˜ˆ์‹œ์—์„œ๋Š” LoggerMiddleware๋ฅผ ์ด์ „์— ์ •์˜ํ•œ CatsController์˜ /cats ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํŠน์ • HTTP ์š”์ฒญ ๋ฉ”์„œ๋“œ์—๋งŒ ์ œํ•œํ•˜๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ, forRoutes() ๋ฉ”์„œ๋“œ์— ๊ฒฝ๋กœ (path)์™€ ํ•จ๊ป˜ ์š”์ฒญ ๋ฉ”์„œ๋“œ (request method)๋ฅผ ํฌํ•จํ•œ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ ์š”์ฒญ ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์ •ํ•˜๋ ค๋ฉด RequestMethod ์—ด๊ฑฐํ˜• (enum)์„ import ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}
ํžŒํŠธ
configure() ๋ฉ”์„œ๋“œ๋Š” async/await์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, configure() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์˜ ์™„๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ฒฝ๊ณ 
Express ์–ด๋Œ‘ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๊ธฐ๋ณธ์ ์œผ๋กœ body-parser ํŒจํ‚ค์ง€์˜ json ๋ฐ urlencoded ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ MiddlewareConsumer๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•˜๋ ค๋ฉด, NestFactory.create()๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ƒ์„ฑํ•  ๋•Œ bodyParser ํ”Œ๋ž˜๊ทธ๋ฅผ false๋กœ ์„ค์ •ํ•˜์—ฌ ์ „์—ญ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ผ์šฐํŠธ ์™€์ผ๋“œ์นด๋“œ

NestJS ๋ฏธ๋“ค์›จ์–ด๋Š” ํŒจํ„ด ๊ธฐ๋ฐ˜ ๋ผ์šฐํŠธ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด๋ฆ„ ์žˆ๋Š” ์™€์ผ๋“œ์นด๋“œ (*splat)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฝ๋กœ์—์„œ ์ž„์˜์˜ ๋ฌธ์ž ์กฐํ•ฉ๊ณผ ๋งค์นญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ๋Š” abcd/๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋Œ€ํ•ด ํ•ด๋‹น ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๊ฒฝ๋กœ ๋’ค์— ๋ช‡ ๊ธ€์ž๊ฐ€ ์˜ค๋“  ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค.

forRoutes({
  path: 'abcd/*splat',
  method: RequestMethod.ALL,
});
ํžŒํŠธ
splat์€ ๋‹จ์ˆœํžˆ ์™€์ผ๋“œ์นด๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ด๋ฆ„์ผ ๋ฟ์ด๋ฉฐ, ํŠน๋ณ„ํ•œ ์˜๋ฏธ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด *wildcard์ฒ˜๋Ÿผ ์›ํ•˜๋Š” ์ด๋ฆ„์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

'abcd/*' ๊ฒฝ๋กœ๋Š” abcd/1, abcd/123, abcd/abc ๋“ฑ๊ณผ ๊ฐ™์ด abcd/๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ์™€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ดํ”ˆ(-)๊ณผ ์ (.)์€ ๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜ ๊ฒฝ๋กœ์—์„œ ๋ฌธ์ž ๊ทธ๋Œ€๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ abcd/์™€ ๊ฐ™์ด ์ถ”๊ฐ€ ๋ฌธ์ž๊ฐ€ ์—†๋Š” ๊ฒฝ๋กœ๋Š” ์ด ๊ฒฝ๋กœ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ์™€์ผ๋“œ์นด๋“œ๋ฅผ ์„ ํƒ์ ์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์ค‘๊ด„ํ˜ธ๋กœ ๊ฐ์‹ธ์•ผํ•ฉ๋‹ˆ๋‹ค.

forRoutes({
  path: 'abcd/{*splat}',
  method: RequestMethod.ALL,
});

๋ฏธ๋“ค์›จ์–ด ์†Œ๋น„์ž

๋ฏธ๋“ค์›จ์–ด ์†Œ๋น„์ž (Middleware consumer)๋Š” NestJS์—์„œ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ—ฌํผ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค๋Š” ์—ฌ๋Ÿฌ ๋‚ด์žฅ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ๋ชจ๋‘ fluent ์Šคํƒ€์ผ๋กœ ์ฒด์ด๋‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. forRoutes() ๋ฉ”์„œ๋“œ๋Š” ๋‹จ์ผ ๋ฌธ์ž์—ด, ์—ฌ๋Ÿฌ ๋ฌธ์ž์—ด, RouteInfo ๊ฐ์ฒด, ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค, ์‹ฌ์ง€์–ด ์—ฌ๋Ÿฌ ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, ๋‹จ์ˆœํžˆ ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์ปจํŠธ๋กค๋Ÿฌ ๋ชฉ๋ก์„ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๋‹จ์ผ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}
ํžŒํŠธ
apply() ๋ฉ”์„œ๋“œ๋Š” ๋‹จ์ผ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๊ณ , ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ์ธ์ˆ˜๋ฅผ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฝ๋กœ ์ œ์™ธํ•˜๊ธฐ

๊ฒฝ์šฐ์— ๋”ฐ๋ผ ํŠน์ • ๊ฒฝ๋กœ์—๋Š” ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š๋„๋ก ์ œ์™ธํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” exclude() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‰ฝ๊ฒŒ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. exclude() ๋ฉ”์„œ๋“œ๋Š” ์ œ์™ธํ•  ๊ฒฝ๋กœ๋ฅผ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ์ผ ๋ฌธ์ž์—ด, ์—ฌ๋Ÿฌ ๋ฌธ์ž์—ด, ๋˜๋Š” RouteInfo ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/{*splat}',
  )
  .forRoutes(CatsController);
ํžŒํŠธ
exclude() ๋ฉ”์„œ๋“œ๋Š” path-to-regexp ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์™€์ผ๋“œ์นด๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

 

์œ„ ์˜ˆ์ œ์—์„œ LoggerMiddleware๋Š” CatsController ๋‚ด์— ์ •์˜๋œ ๋ชจ๋“  ๋ผ์šฐํŠธ์— ์ ์šฉ๋˜์ง€๋งŒ, exclude() ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌ๋œ ์„ธ ๊ฐ€์ง€ ๋ผ์šฐํŠธ๋Š” ์˜ˆ์™ธ์ž…๋‹ˆ๋‹ค.

 

์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ํŠน์ • ๋ผ์šฐํŠธ๋‚˜ ๋ผ์šฐํŠธ ํŒจํ„ด์— ๋”ฐ๋ผ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•˜๊ฑฐ๋‚˜ ์ œ์™ธํ•˜๋Š” ๋ฐ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํ•จ์ˆ˜ํ˜• ๋ฏธ๋“ค์›จ์–ด

์ง€๊ธˆ๊นŒ์ง€ ์‚ฌ์šฉํ•œ LoggerMiddleware ํด๋ž˜์Šค๋Š” ๋งค์šฐ ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. ๋ฉค๋ฒ„๋„ ์—†๊ณ , ์ถ”๊ฐ€ ๋ฉ”์„œ๋“œ๋‚˜ ์˜์กด์„ฑ๋„ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ด๊ฑธ ๊ตณ์ด ํด๋ž˜์Šค๋กœ ์ •์˜ํ•˜์ง€ ์•Š๊ณ , ๊ฐ„๋‹จํ•œ ํ•จ์ˆ˜๋กœ ์ •์˜ํ•  ์ˆ˜๋Š” ์—†์„๊นŒ์š”? ์‹ค์ œ๋กœ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ•จ์ˆ˜ํ˜• ๋ฏธ๋“ค์›จ์–ด (functional middleware)๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ•จ์ˆ˜ํ˜•์œผ๋กœ ๋ณ€ํ™˜ํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

 

๊ทธ๋ฆฌ๊ณ  AppModule ๋‚ด์—์„œ ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

consumer
  .apply(logger)
  .forRoutes(CatsController);
ํžŒํŠธ
๋ฏธ๋“ค์›จ์–ด์— ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด, ๋” ๊ฐ„๋‹จํ•œ ํ•จ์ˆ˜ํ˜• ๋ฏธ๋“ค์›จ์–ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

๋‹ค์ค‘ ๋ฏธ๋“ค์›จ์–ด

์•ž์„œ ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜๋ ค๋ฉด apply() ๋ฉ”์„œ๋“œ ์•ˆ์— ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ๋ชฉ๋ก์„ ์ œ๊ณตํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

์ „์—ญ ๋ฏธ๋“ค์›จ์–ด

๋ชจ๋“  ๋“ฑ๋ก๋œ ๋ผ์šฐํŠธ์— ํ•œ ๋ฒˆ์— ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, INestApplication ์ธ์Šคํ„ด์Šค๊ฐ€ ์ œ๊ณตํ•˜๋Š” use() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(process.env.PORT ?? 3000);
ํžŒํŠธ
์ „์—ญ ๋ฏธ๋“ค์›จ์–ด์—์„œ DI ์ปจํ…Œ์ด๋„ˆ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. app.use()๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ํ•จ์ˆ˜ํ˜• ๋ฏธ๋“ค์›จ์–ด (functional middleware)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , AppModule (๋˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“ˆ) ๋‚ด์—์„œ .forRoutes('*')๋กœ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์™ธ ํ•„ํ„ฐ

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

๊ธฐ๋ณธ์ ์œผ๋กœ ์ด ๋™์ž‘์€ ์ „์—ญ ๋‚ด์žฅ ์˜ˆ์™ธ ํ•„ํ„ฐ์— ์˜ํ•ด ์ˆ˜ํ–‰๋˜๋ฉฐ, ์ด ํ•„ํ„ฐ๋Š” HttpException ๋ฐ ๊ทธ ํ•˜์œ„ ํด๋ž˜์Šค ํƒ€์ž…์˜ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ๊ฐ€ HttpException์ด ์•„๋‹ˆ๊ฑฐ๋‚˜ ์ด๋ฅผ ์ƒ์†๋ฐ›์€ ํด๋ž˜์Šค๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ, ๋‚ด์žฅ ์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋ณธ JSON ์‘๋‹ต์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค:

{
  "statusCode": 500,
  "message": "Internal server error"
}
ํžŒํŠธ
์ „์—ญ ์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” http-errors ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ถ€๋ถ„์ ์œผ๋กœ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ, statusCoide์™€ message ์†์„ฑ์„ ํฌํ•จํ•˜๋Š” ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ํ•ด๋‹น ์ •๋ณด๊ฐ€ ์ œ๋Œ€๋กœ ๋ฐ˜์˜๋˜์–ด ์‘๋‹ต์œผ๋กœ ์ „์†ก๋ฉ๋‹ˆ๋‹ค (์ธ์‹๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ์— ๋Œ€ํ•ด ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” InternalServerErrorException ๋Œ€์‹ ์—).

ํ‘œ์ค€ ์˜ˆ์™ธ ๋˜์ง€๊ธฐ

Nest๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ ์ œ๊ณต๋˜๋Š” ๋‚ด์žฅ HttpException ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ HTTP REST/GraphQL API ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ํŠน์ • ์˜ค๋ฅ˜ ์กฐ๊ฑด์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ‘œ์ค€ HTTP ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ๋ชจ๋ฒ” ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด, CatsController์— findAll() ๋ฉ”์„œ๋“œ (GET ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ)๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค. ์ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์–ด๋–ค ์ด์œ ๋กœ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๋“œ์ฝ”๋”ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

 

ํžŒํŠธ
์—ฌ๊ธฐ์„œ ์‚ฌ์šฉํ•œ HttpStatus๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜จ ํ—ฌํผ enum์ž…๋‹ˆ๋‹ค.

 

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
  "statusCode": 403,
  "message": "Forbidden"
}

 

HttpException ์ƒ์„ฑ์ž๋Š” ์‘๋‹ต์„ ๊ฒฐ์ •ํ•˜๋Š” ๋‘ ๊ฐœ์˜ ํ•„์ˆ˜ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค:

  • response ์ธ์ž๋Š” JSON ์‘๋‹ต ๋ณธ๋ฌธ์„ ๊ฒฐ์ •ํ•˜๋ฉฐ, ๋ฌธ์ž์—ด์ด๋‚˜ ๊ฐ์ฒด๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • status ์ธ์ž๋Š” HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ JSON ์‘๋‹ต ๋ณธ๋ฌธ์—๋Š” ๋‘ ๊ฐœ์˜ ์†์„ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

  • statusCode: status ์ธ์ž๋กœ ์ œ๊ณต๋œ HTTP ์ƒํƒœ ์ฝ”๋“œ
  • message: ํ•ด๋‹น ์ƒํƒœ ์ฝ”๋“œ์— ๊ธฐ๋ฐ˜ํ•œ ์งง์€ ์˜ค๋ฅ˜ ์„ค๋ช…

message๋งŒ ๋ฎ์–ด์“ฐ๊ณ  ์‹ถ๋‹ค๋ฉด response ์ธ์ž์— ๋ฌธ์ž์—ด์„ ์ „๋‹ฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์‘๋‹ต ๋ณธ๋ฌธ ์ „์ฒด๋ฅผ ๋ฎ์–ด์“ฐ๋ ค๋ฉด response ์ธ์ž์— ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ๋˜๊ณ , Nest๊ฐ€ ์ด๋ฅผ JSON์œผ๋กœ ์ง๋ ฌํ™”ํ•˜์—ฌ ์‘๋‹ต ๋ณธ๋ฌธ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

 

๋‘ ๋ฒˆ์งธ ์ธ์ž์ธ status๋Š” ์œ ํšจํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ์—ฌ์•ผ ํ•˜๋ฉฐ, ๋ณดํ†ต @nestjs/common์—์„œ ์ œ๊ณตํ•˜๋Š” HttpStatus enum์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

 

์„ ํƒ์ ์ธ ์„ธ ๋ฒˆ์งธ ์ธ์ž์ธ options๋Š” ์˜ค๋ฅ˜์˜ ์›์ธ์„ ๋‹ด์€ cause ๊ฐ์ฒด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด cause๋Š” ์‘๋‹ต ๊ฐ์ฒด์— ์ง๋ ฌํ™”๋˜์ง„ ์•Š์ง€๋งŒ, ๋กœ๊น… ์‹œ ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, HttpException์ด ๋ฐœ์ƒํ•œ ๋‚ด๋ถ€ ์›์ธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋‹ค์Œ์€ ์‘๋‹ต ๋ณธ๋ฌธ ์ „์ฒด๋ฅผ ๋ฎ์–ด์“ฐ๊ณ  ์˜ค๋ฅ˜ ์›์ธ์„ ์ œ๊ณตํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

@Get()
async findAll() {
  try {
    await this.service.findAll()
  } catch (error) {
    throw new HttpException({
      status: HttpStatus.FORBIDDEN,
      error: 'This is a custom message',
    }, HttpStatus.FORBIDDEN, {
      cause: error
    });
  }
}

 

์œ„์™€ ๊ฐ™์ด ์ „์ฒด ์‘๋‹ต์„ ๊ฐ์ฒด๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜๋ฉด, ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜๋˜๋Š” ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
  "status": 403,
  "error": "This is a custom message"
}

์˜ˆ์™ธ ๋กœ๊น…

๊ธฐ๋ณธ์ ์œผ๋กœ, Nest์˜ ์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” HttpException๊ณผ ๊ทธ๊ฒƒ์„ ์ƒ์†ํ•œ ์˜ˆ์™ธ๋“ค๊ณผ ๊ฐ™์€ ๋‚ด์žฅ ์˜ˆ์™ธ๋ฅผ ๋กœ๊น…ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์ฝ˜์†”์— ํ‘œ์‹œ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์ด๋Š” ์ผ๋ฐ˜์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ๋ฆ„์˜ ์ผ๋ถ€๋กœ ๊ฐ„์ฃผ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด์™€ ๋™์ผํ•œ ๋™์ž‘์€ WsException, RpcException ๊ฐ™์€ ๋‹ค๋ฅธ ๋‚ด์žฅ ์˜ˆ์™ธ์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๋“ค์€ ๋ชจ๋‘ @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๋‚ด๋ณด๋‚ด๋Š” IntrinsicException ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค๋Š” ์ผ๋ฐ˜์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋™์ž‘์˜ ์ผ๋ถ€์ธ ์˜ˆ์™ธ์™€ ๊ทธ๋ ‡์ง€ ์•Š์€ ์˜ˆ์™ธ๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

 

์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๋ฅผ ๋กœ๊น…ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์ปค์Šคํ…€ ์˜ˆ์™ธ ํ•„ํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ ๊ทธ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์ปค์Šคํ…€ ์˜ˆ์™ธ

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, ์ปค์Šคํ…€ ์˜ˆ์™ธ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š” ์—†์ด Nest์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‚ด์žฅ HTTP ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ปค์Šคํ…€ ์˜ˆ์™ธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š”, HttpException ํด๋ž˜์Šค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์˜ˆ์™ธ ๊ณ„์ธต์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€ ๊ด€ํ–‰์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด, Nest๊ฐ€ ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์ธ์‹ํ•˜๊ณ  ์ž๋™์œผ๋กœ ์˜ค๋ฅ˜ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ด๋Ÿฌํ•œ ์ปค์Šคํ…€ ์˜ˆ์™ธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

 

ForbiddenException์€ ๊ธฐ๋ณธ HttpException ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๋ฏ€๋กœ, Nest์˜ ๋‚ด์žฅ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ์™€ ์›ํ™œํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ findAll() ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Get()
async findAll() {
  throw new ForbiddenException();
}

๋‚ด์žฅ HTTP ์˜ˆ์™ธ๋“ค

Nest๋Š” ๊ธฐ๋ณธ HttpException์„ ์ƒ์†ํ•˜๋Š” ํ‘œ์ค€ ์˜ˆ์™ธ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์™ธ๋“ค์€ @nestjs/common ํŒจํ‚ค์ง€์—์„œ ์ œ๊ณต๋˜๋ฉฐ, ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ HTTP ์˜ˆ์™ธ๋“ค์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException

๋ชจ๋“  ๋‚ด์žฅ ์˜ˆ์™ธ๋Š” options ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ ์›์ธ (cause)๊ณผ ์—๋Ÿฌ ์„ค๋ช… (description)์„ ํ•จ๊ป˜ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

throw new BadRequestException('Something bad happened', {
  cause: new Error(),
  description: 'Some error description',
});

 

์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด, ์‘๋‹ต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

{
  "message": "Something bad happened",
  "error": "Some error description",
  "statusCode": 400
}

์˜ˆ์™ธ ํ•„ํ„ฐ

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

 

์ด์ œ HttpException ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ํฌ์ฐฉํ•˜๊ณ , ๊ทธ์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ์˜ˆ์™ธ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๊ธฐ๋ณธ ํ”Œ๋žซํผ์˜ Request ๋ฐ Response ๊ฐ์ฒด์— ์ ‘๊ทผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Request ๊ฐ์ฒด์—์„œ๋Š” ์›๋ž˜์˜ URL์„ ์ถ”์ถœํ•ด ๋กœ๊น… ์ •๋ณด์— ํฌํ•จ์‹œํ‚ค๊ณ , Response ๊ฐ์ฒด์—์„œ๋Š” response.json() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์‘๋‹ต์„ ์ง์ ‘ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}
ํžŒํŠธ
๋ชจ๋“  ์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” ์ œ๋„ค๋ฆญ ExceptionFilter<T> ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” catch(exception: T, host: ArgumentsHost) ๋ฉ”์„œ๋“œ๋ฅผ ํ•ด๋‹น ์‹œ๊ทธ๋‹ˆ์ฒ˜๋กœ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ T๋Š” ์˜ˆ์™ธ์˜ ํƒ€์ž…์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
๊ฒฝ๊ณ 
@nestjs/platform-fastify๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, response.json() ๋Œ€์‹  response.send()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. fastify์—์„œ ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์„ ์ž„ํฌํŠธ ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”.

 

@Catch(HttpException) ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ์˜ˆ์™ธ ํ•„ํ„ฐ์— ํ•„์š”ํ•œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ, Nest์—๊ฒŒ ์ด ํ•„ํ„ฐ๊ฐ€ HttpException ํƒ€์ž…์˜ ์˜ˆ์™ธ๋งŒ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์•Œ๋ ค์ค๋‹ˆ๋‹ค. @Catch() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํ•˜๋‚˜์˜ ์ธ์ž๋ฅผ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ๊ณ , ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์—ฌ๋Ÿฌ ์ธ์ž๋ฅผ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์˜ˆ์™ธ๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ํ•„ํ„ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Arguments host

catch() ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. exception ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ˜„์žฌ ์ฒ˜๋ฆฌ ์ค‘์ธ ์˜ˆ์™ธ ๊ฐ์ฒด์ด๋ฉฐ, host ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ArgumentsHost ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ArgumentsHost๋Š” ๋งค์šฐ ๊ฐ•๋ ฅํ•œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ฐ์ฒด๋กœ, ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์ฑ•ํ„ฐ์—์„œ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

 

์ด ์ฝ”๋“œ ์˜ˆ์ œ์—์„œ๋Š” ArgumentsHost๋ฅผ ์‚ฌ์šฉํ•ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ์ปจํŠธ๋กค๋Ÿฌ์˜ ์›๋ž˜ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋œ Request์™€ Response ๊ฐ์ฒด์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ArgumentsHost์— ์ •์˜๋œ ํ—ฌํผ ๋ฉ”์„œ๋“œ๋“ค์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค. ArgumentsHost์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด๋ ค๋ฉด ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•˜์„ธ์š”.

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

ํ•„ํ„ฐ ๋ฐ”์ธ๋”ฉํ•˜๊ธฐ

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  HttpExceptionFilter๋ฅผ CatsController์˜ create() ๋ฉ”์„œ๋“œ์— ์—ฐ๊ฒฐํ•ด ๋ด…์‹œ๋‹ค.

@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
ํžŒํŠธ
@UseFilters() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” @UseFilters() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. @Catch() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋‹จ์ผ ํ•„ํ„ฐ ์ธ์Šคํ„ด์Šค ๋˜๋Š” ์‰ผํ‘œ๋กœ ๊ตฌ๋ถ„๋œ ์—ฌ๋Ÿฌ ํ•„ํ„ฐ ์ธ์Šคํ„ด์Šค๋ฅผ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” HttpExceptionFilter ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•ด ์ „๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ํด๋ž˜์Šค ์ž์ฒด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ Nest ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ํ•  ์ˆ˜๋„ ์žˆ์œผ๋ฉฐ, ์ด ๊ฒฝ์šฐ ์˜์กด์„ฑ ์ฃผ์ž…๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
ํžŒํŠธ
๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ํ•„ํ„ฐ๋ฅผ ์ธ์Šคํ„ด์Šค๊ฐ€ ์•„๋‹Œ ํด๋ž˜์Šค๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Nest๋Š” ๋™์ผํ•œ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ชจ๋“ˆ ์ „์ฒด์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” HttpExceptionFilter๊ฐ€ ๋‹จ์ผ create() ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์—๋งŒ ์ ์šฉ๋˜์–ด ๋ฉ”์„œ๋“œ ๋ฒ”์œ„ (method-scoped)๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” ๋ฉ”์„œ๋“œ ๋ฒ”์œ„ (method-scoped), ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ”์œ„ (controller-scoped), ๋˜๋Š” ์ „์—ญ ๋ฒ”์œ„ (global-scoped)๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ”์œ„๋กœ ํ•„ํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

@Controller()
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

 

์ด ๊ตฌ์„ฑ์€ CatsController ๋‚ด์— ์ •์˜๋œ ๋ชจ๋“  ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•ด HttpExceptionFilter๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

 

์ „์—ญ ๋ฒ”์œ„ (global-scoped)์˜ ํ•„ํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
๊ฒฝ๊ณ 
useGlobalFilters() ๋ฉ”์„œ๋“œ๋Š” ๊ฒŒ์ดํŠธ์›จ์ด (gateway)๋‚˜ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (hybrid applications)์—๋Š” ํ•„ํ„ฐ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์ „์—ญ ๋ฒ”์œ„ ํ•„ํ„ฐ (Global-scoped filters)๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์— ๊ฑธ์ณ ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ ๋ฐ ๋ชจ๋“  ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ์ฃผ์ž… (Dependency Injection) ์ธก๋ฉด์—์„œ ๋ณด๋ฉด, useGlobalFilters()๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ๋“ˆ ์™ธ๋ถ€์—์„œ ๋“ฑ๋กํ•œ ์ „์—ญ ํ•„ํ„ฐ๋Š” ์˜์กด์„ฑ์„ ์ฃผ์ž…๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” Nest์˜ ๋ชจ๋“ˆ ์ปจํ…์ŠคํŠธ ๋ฐ”๊นฅ์—์„œ ํ•„ํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด, ๋ชจ๋“ˆ ๋‚ด๋ถ€์—์„œ ์ „์—ญ ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

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

 

์ด ๋ฐฉ์‹์œผ๋กœ ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•  ๋•Œ๋Š” ํ•„์š”ํ•œ ๋งŒํผ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ๊ฐ๊ฐ์˜ ํ•„ํ„ฐ๋ฅผ providers ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Catch everything

๋ชจ๋“  ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๋ ค๋ฉด, @Catch() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋น„์›Œ๋‘๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ฆ‰, @Catch()์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์•„๋ž˜ ์˜ˆ์‹œ๋Š” ํ”Œ๋žซํผ์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ๋กœ, Request๋‚˜ Response ๊ฐ™์€ ํ”Œ๋žซํผ ํŠน์ • ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , Nest์˜ HTTP ์–ด๋Œ‘ํ„ฐ๋ฅผ ํ†ตํ•ด ์‘๋‹ต์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';

@Catch()
export class CatchEverythingFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    // ํŠน์ • ์ƒํ™ฉ์—์„œ๋Š” `httpAdapter`๊ฐ€ ์ƒ์„ฑ์ž ๋ฉ”์„œ๋“œ์—์„œ ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ,
    // ์—ฌ๊ธฐ์„œ ์ด๋ฅผ ํ•ด๊ฒฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    const { httpAdapter } = this.httpAdapterHost;

    const ctx = host.switchToHttp();

    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const responseBody = {
      statusCode: httpStatus,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}
๊ฒฝ๊ณ 
๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๋Š” ํ•„ํ„ฐ์™€ ํŠน์ • ํƒ€์ž…์— ๋ฐ”์ธ๋”ฉ๋œ ํ•„ํ„ฐ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, "๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๋Š”" ํ•„ํ„ฐ๋ฅผ ๋จผ์ € ์„ ์–ธํ•ด์•ผ ํŠน์ • ํƒ€์ž…์— ๋ฐ”์ธ๋”ฉ๋œ ํ•„ํ„ฐ๊ฐ€ ํ•ด๋‹น ํƒ€์ž…์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒ์†

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

 

์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ธฐ๋ณธ ํ•„ํ„ฐ์— ์œ„์ž„ํ•˜๋ ค๋ฉด, BaseExceptionFilter๋ฅผ ํ™•์žฅํ•˜๊ณ  ์ƒ์†๋ฐ›์€ catch() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    super.catch(exception, host);
  }
}
๊ฒฝ๊ณ 
BaseExceptionFilter๋ฅผ ํ™•์žฅํ•œ ๋ฉ”์„œ๋“œ ๋ฒ”์œ„ ๋˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ”์œ„ ํ•„ํ„ฐ๋Š” new ํ‚ค์›Œ๋“œ๋กœ ์ง์ ‘ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  Nest ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ž๋™์œผ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์ „์—ญ ํ•„ํ„ฐ๋Š” ๊ธฐ๋ณธ ํ•„ํ„ฐ๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ์ž‘์—…์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ์‚ฌ์šฉ์ž ์ •์˜ ์ „์—ญ ํ•„ํ„ฐ๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•  ๋•Œ HttpAdapter ์ฐธ์กฐ๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const { httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

 

๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ APP_FILTER ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์™€ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Reference