Exception filters

Exception filters

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

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

{
  "statusCode": 500,
  "message": "Internal server error"
}

Base exceptions

๋‚ด์žฅ ๋œ HttpExceptionํด๋ž˜์Šค๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ณต๊ฐœ๋ฉ๋‹ˆ๋‹ค.

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

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

info ํžŒํŠธ ์—ฌ๊ธฐ์„œ๋Š” HttpStatus๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜จ ํ—ฌํผ ์—ด๊ฑฐํ˜•์ž…๋‹ˆ๋‹ค.

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

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

HttpException ์ƒ์„ฑ์ž๋Š” ์‘๋‹ต์„ ๊ฒฐ์ •ํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ํ•„์ˆ˜ ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • response ์ธ์ˆ˜๋Š” JSON ์‘๋‹ต ๋ณธ๋ฌธ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์— ์„ค๋ช… ๋œ๋Œ€๋กœ ๋ฌธ์ž์—ด ๋˜๋Š” ๊ฐ์ฒด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • status ์ธ์ˆ˜๋Š” HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ JSON ์‘๋‹ต ๋ณธ๋ฌธ์—๋Š” ๋‘ ๊ฐ€์ง€ ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • statusCode: status ์ธ์ˆ˜์— ์ œ๊ณต๋œ HTTP ์ƒํƒœ ์ฝ”๋“œ๋กœ ๊ธฐ๋ณธ ์„ค์ •

  • message: ์ƒํƒœ์— ๊ธฐ๋ฐ˜ํ•œ HTTP ์—๋Ÿฌ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์„ค๋ช…

JSON ์‘๋‹ต ๋ณธ๋ฌธ์˜ ๋ฉ”์‹œ์ง€ ๋ถ€๋ถ„ ๋งŒ ์žฌ์ •์˜ํ•˜๋ ค๋ฉด response ์ธ์ˆ˜์— ๋ฌธ์ž์—ด์„ ์ œ๊ณตํ•˜์‹ญ์‹œ์˜ค.

์ „์ฒด JSON ์‘๋‹ต ๋ณธ๋ฌธ์„ ์žฌ์ •์˜ํ•˜๋ ค๋ฉด response ์ธ์ˆ˜์— ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜์‹ญ์‹œ์˜ค. Nest๋Š” ๊ฐ์ฒด๋ฅผ ์ง๋ ฌํ™”ํ•˜์—ฌ JSON ์‘๋‹ต ๋ณธ๋ฌธ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ์ƒ์„ฑ์ž ์ธ์ž status๋Š” ์œ ํšจํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ ์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋ฒ” ์‚ฌ๋ก€๋Š”@nestjs/common์—์„œ ๊ฐ€์ ธ์˜จ HttpStatus ์—ด๊ฑฐ ํ˜•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์ „์ฒด ์‘๋‹ต ๋ณธ๋ฌธ์„ ์žฌ์ •์˜ ํ•˜๋Š” ์˜ˆ์ž…๋‹ˆ๋‹ค.

@@filename(cats.controller)
@Get()
async findAll() {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, 403);
}

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

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

Exceptions hierarchy

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

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

ForbiddenException์€ ๊ธฐ๋ณธ HttpException์„ ํ™•์žฅํ•˜๋ฏ€๋กœ ๋‚ด์žฅ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ์™€ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋ฏ€๋กœ findAll() ๋ฉ”์†Œ๋“œ ๋‚ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@@filename(cats.controller)
@Get()
async findAll() {
  throw new ForbiddenException();
}

HTTP exceptions

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

  • BadRequestException

  • UnauthorizedException

  • NotFoundException

  • ForbiddenException

  • NotAcceptableException

  • RequestTimeoutException

  • ConflictException

  • GoneException

  • PayloadTooLargeException

  • UnsupportedMediaTypeException

  • UnprocessableEntityException

  • InternalServerErrorException

  • NotImplementedException

  • BadGatewayException

  • ServiceUnavailableException

  • GatewayTimeoutException

Exception filters

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

HttpExceptionํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค์ธ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•˜๊ณ  ์ด์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ ์‘๋‹ต ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ์˜ˆ์™ธ ํ•„ํ„ฐ๋ฅผ ์ž‘์„ฑํ•ด ๋ด…์‹œ๋‹ค.

@@filename(http-exception.filter)
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,
      });
  }
}
@@switch
import { Catch, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter {
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
      });
  }
}

info ํžŒํŠธ ๋ชจ๋“  ์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” ์ผ๋ฐ˜ ExceptionFilter <T> ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š”catch (exception: T, host: ArgumentsHost)๋ฉ”์†Œ๋“œ์— ํ‘œ์‹œ๋œ ์„œ๋ช…์„ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. T๋Š” ์˜ˆ์™ธ ์œ ํ˜•์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

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

Arguments host

catch() ๋ฉ”์†Œ๋“œ์˜ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ๋ณด์ž. exception ๋งค๊ฐœ ๋ณ€์ˆ˜๋Š” ํ˜„์žฌ ์ฒ˜๋ฆฌ์ค‘์ธ ์˜ˆ์™ธ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. host ๋งค๊ฐœ ๋ณ€์ˆ˜๋Š” ArgumentsHost ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ArgumentsHost๋Š” original ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ (์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ณณ)์— ์ „๋‹ฌ๋œ ์ธ์ˆ˜๋ฅผ ๊ฐ์‹ธ๋Š” ๋ž˜ํผ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ๋ฐ ์‚ฌ์šฉ์ค‘์ธ ํ”Œ๋žซํผ์˜ ์œ ํ˜•์— ๋”ฐ๋ผ ํŠน์ • ์ธ์ˆ˜ ๋ฐฐ์—ด์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ArgumentsHost์˜ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค :

export interface ArgumentsHost {
  getArgs<T extends Array<any> = any[]>(): T;
  getArgByIndex<T = any>(index: number): T;
  switchToRpc(): RpcArgumentsHost;
  switchToHttp(): HttpArgumentsHost;
  switchToWs(): WsArgumentsHost;
}

ArgumentsHost๋Š” ๋‹ค์–‘ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์—์„œ ๊ธฐ๋ณธ ๋ฐฐ์—ด์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์ธ์ˆ˜๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ํŽธ๋ฆฌํ•œ ๋ฉ”์†Œ๋“œ ์„ธํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•ด, ArgumentsHost๋Š” ์ธ์ž ๋ฐฐ์—ด์— ์ง€๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, HTTP ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ArgumentsHost์— [request, response]๋ฐฐ์—ด์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ˜„์žฌ ์ปจํ…์ŠคํŠธ๊ฐ€ ์›น ์†Œ์ผ“ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ธ ๊ฒฝ์šฐ ํ•ด๋‹น ์ปจํ…์ŠคํŠธ์— ์ ํ•ฉํ•œ [client, data]๋ฐฐ์—ด์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž ์ •์˜ catch()๋ฉ”์†Œ๋“œ์—์„œ ์›๋ž˜ ํ•ธ๋“ค๋Ÿฌ๋กœ ์ „๋‹ฌ๋˜๋Š” ์ธ์ˆ˜์— ์•ก์„ธ์Šค ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Binding filters

์ƒˆ๋กœ์šด HttpExceptionFilter๋ฅผ CatsController์˜ create()๋ฉ”์†Œ๋“œ์— ๋ฌถ์–ด ๋ด…์‹œ๋‹ค.

@@filename(cats.controller)
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
@@switch
@Post()
@UseFilters(new HttpExceptionFilter())
@Bind(Body())
async create(createCatDto) {
  throw new ForbiddenException();
}

info ํžŒํŠธ @UseFilters() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” @nestjs/common ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

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

@@filename(cats.controller)
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
@@switch
@Post()
@UseFilters(HttpExceptionFilter)
@Bind(Body())
async create(createCatDto) {
  throw new ForbiddenException();
}

info ํžŒํŠธ ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ์ธ์Šคํ„ด์Šค ๋Œ€์‹  ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. Nest๊ฐ€ ์ „์ฒด ๋ชจ๋“ˆ์—์„œ ๋™์ผํ•œ ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์‰ฝ๊ฒŒ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ค„์–ด ๋“ญ๋‹ˆ๋‹ค.

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

@@filename(cats.controller)
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

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

์ „์—ญ ๋ฒ”์œ„ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์‹ญ์‹œ์˜ค.

@@filename(main)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

warning ๊ฒฝ๊ณ  useGlobalFilters () ๋ฉ”์†Œ๋“œ๋Š” ๊ฒŒ์ดํŠธ์›จ์ด ๋˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ํ•„ํ„ฐ๋ฅผ ์„ค์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

@@filename(app.module)
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

info ํžŒํŠธ ์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ์— ๋Œ€ํ•œ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์ด ๊ตฌ์„ฑ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ชจ๋“ˆ์— ๊ด€๊ณ„์—†์ด ํ•„ํ„ฐ๋Š” ์‹ค์ œ๋กœ ์ „์—ญ์ ์ž…๋‹ˆ๋‹ค. ์–ด๋””์—์„œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ? ํ•„ํ„ฐ (์œ„ ์˜ˆ์—์„œ HttpExceptionFilter)๊ฐ€ ์ •์˜ ๋œ ๋ชจ๋“ˆ์„ ์„ ํƒํ•˜์‹ญ์‹œ์˜ค. ๋˜ํ•œ ์ปค์Šคํ…€ ํ”„๋กœ ๋ฐ”์ด๋” ๋“ฑ๋ก์„ ๋‹ค๋ฃจ๋Š” ์œ ์ผํ•œ ๋ฐฉ๋ฒ•์€ useClass๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์‹ญ์‹œ์˜ค.

์ด ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ๋งŒํผ ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํžˆ ๊ณต๊ธ‰์ž ๋ฐฐ์—ด์— ๊ฐ๊ฐ์„ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.

Catch everything

์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ (์˜ˆ์™ธ ์œ ํ˜•์— ๊ด€๊ณ„์—†์ด)๋ฅผ ๋ชจ๋‘ ์žก์œผ๋ ค๋ฉด @Catch()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์˜ ๋งค๊ฐœ ๋ณ€์ˆ˜ ๋ชฉ๋ก์„ ๋น„์›Œ ๋‘์‹ญ์‹œ์˜ค (์˜ˆ: @Catch()).

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

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

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

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

์œ„์˜ ์˜ˆ์—์„œ ํ•„ํ„ฐ๋Š” ์œ ํ˜• (ํด๋ž˜์Šค)์— ๊ด€๊ณ„์—†์ด ๋ฐœ์ƒ ๋œ ๊ฐ ์˜ˆ์™ธ๋ฅผ ํฌ์ฐฉํ•ฉ๋‹ˆ๋‹ค.

Inheritance

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

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

@@filename(all-exceptions.filter)
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);
  }
}
@@switch
import { Catch } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
  catch(exception, host) {
    super.catch(exception, host);
  }
}

warning ๊ฒฝ๊ณ  BaseExceptionFilter๋ฅผ ํ™•์žฅํ•˜๋Š” ๋ฉ”์„œ๋“œ ๋ฒ”์œ„ ๋ฐ ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ”์œ„ ํ•„ํ„ฐ๋Š” new๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค. ๋Œ€์‹  ํ”„๋ ˆ์ž„ ์›Œํฌ์—์„œ ์ž๋™์œผ๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•˜์‹ญ์‹œ์˜ค.

์œ„์˜ ๊ตฌํ˜„์€ ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋ณด์—ฌ์ฃผ๋Š” ์‰˜์ผ๋ฟ์ž…๋‹ˆ๋‹ค. ํ™•์žฅ ์˜ˆ์™ธ ํ•„ํ„ฐ ๊ตฌํ˜„์—๋Š” ๋งž์ถคํ˜• ๋น„์ฆˆ๋‹ˆ์Šค ๋…ผ๋ฆฌ (์˜ˆ: ๋‹ค์–‘ํ•œ ์กฐ๊ฑด ์ฒ˜๋ฆฌ)๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

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

์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์€ ์ปค์Šคํ…€ ๊ธ€๋กœ๋ฒŒ ํ•„ํ„ฐ๋ฅผ ์ธ์Šคํ„ด์Šคํ™” ํ•  ๋•Œ HttpServer ์ฐธ์กฐ๋ฅผ ์‚ฝ์ž…ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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

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

  await app.listen(3000);
}
bootstrap();

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

Last updated

Was this helpful?