NextJS Exception Filters

paduck·2024년 6월 12일
0

NestJS

목록 보기
7/24

내장된 예외 처리 레이어가 존재

  • 모든 예외에 적용
  • 다뤄지지 않은 예외는 해당 레이어로 가며, 사용자 친화적인 메시지 응답

HttpException 을 내장된 전역 예외 처리 필터에서 처리됨

  • 해당되는 예외 혹은 하위 예외가 아닐 경우 unrecognized 로 해당 응답 반환
{
  "statusCode": 500,
  "message": "Internal server error"
}

전역 예외 필터는 http-errors 라이브러리를 부분적으로 지원합니다. statusCodemessage 를 포함한 에러는 적절하게 합쳐지고 응답(확인되지 않은 InternalServerErrorException 예외 대신에)으로써 반환됩니다

Thorwing standard exceptions

내장된 @nestjs/common 패키지에서 비롯된 HttpException 클래스를 제공

  • 특정 HTTP Rest/GraphQL API 에 대해 표준 HTTP 응답을 반환하는 것이 바람직
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

@nestjs/common 패키지에서 비롯된 helper enum 입니다.

  • 이것에 대해
{
  "statusCode": 403,
  "message": "Forbidden"
}

HttpException 생성자는 response 와 status 라는 두 가지 인자를 받음

  • response 은 JSON 응답 본문이며, string 혹은 object 가능
    • object 를 넘겨서 JSON 응답 본문 대체 가능
    • statusCode 은 HTTP status code
    • message는 HTTP error 에 대한 짧은 설명
      • response 에 문자열을 넘김으로써 대체 가능
  • statusHTTP Status Code 를 의미
    • 올바른 HTTP Status code 가 들어와야하며, @nestjs/commonHttpStatus enum 을 쓰는게 제일 바람직
  • error의 이유 에 해당하는 options 속성은 응답 객체에 들어가진 않으나, 로깅에 매우 유용
    • 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
    });
  }
}
  • error 반환이며
{
  "status": 403,
  "error": "This is a custom message"
}
  • 그에 대한 응답

Custom exceptions

HttpException 을 상속하는 개인화된 예외 계층 만들기

export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}
@Get()
async findAll() {
  throw new ForbiddenException();
}

일반적인 예외 처리와 동일하게 적용

Built-in HTTP exceptions

HttpException 으로부터 상속받고, @nestjs/common 패키지에 비롯

Exception filters

예외 계층에 대한 완전한 컨트롤

  • 사용자에게 반환될 응답의 내용과 예외 flow 에 대해 컨트롤 가능
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) 데코레이터는 HttpException에 해당하는 특정 예외만 본다는 얘기

  • 단일-복수 파라미터 가능

Arguments host

catch() 함수의 인자

  • exception 은 현재 진행 중인 예외 객체
  • hostArgumentsHost 객체

Nest 모든 context 에 있기 때문에 추상화 수준 필요했음

Binding filters

@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

@UseFilters() 데코레이터는 @nestjs/common 패키지에 있습니다.

  • 단일-복수 filter 인스턴스를 인자로 받음
  • 인스턴스가 아닌 클래스 자체를 넘겨, IoC 및 DI 가능

가능할 경우 인스턴스보다 클래스를 통해 필터를 적용허세요. 동일한 클래스의 인스턴스를 재사용할 수 있어 메모리 사용량이 줄어듭니다.

함수 레벨, 컨트롤러 레벨, 전역 레벨 에서 스코핑 가능

// 컨트롤러 레벨
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
// 전역 레벨
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

useGlobalFilters() 함수는 게이트웨이 나 하이브리드 앱에는 적용되지 않습니다.

useGlobalFilters() 를 사용하는 경우 DI 를 적용할 수 없음

  • 어느 module의 context 외부에서 수행되기 때문
// DI 문제를 해결한 방식

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

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

DI 문제를 해결한 방식은 모듈에 상관없이 필터는 전역적입니다. 필터가 적용된 모듈을 선택하세요. 그리고, useClass 만이 커스텀 provider 를 등록하는 방법은 아닙니다. 여기

Catch everything

  • @Catch() 에 변수를 주지 않으면 됌
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';

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

  catch(exception: unknown, host: ArgumentsHost): void {
    // In certain situations `httpAdapter` might not be available in the
    // constructor method, thus we should resolve it here.
    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);
  }
}
  • 플랫폼에 특정화된 객체를 쓰지 않고, HTTP adapter 를 통해 플랫폼-친화적 응답 전달

모든 것을 포착하는 예외 필터와 특정 유형에 바인딩된 필터를 결합할 때 특정 필터가 바인딩된 유형을 올바르게 처리할 수 있도록 "Catch everything" 필터를 먼저 선언해야 합니다.

Inheritance

내장된 전역 예외 필터 를 확장해 재정의 하고 싶은 경우

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 로 인스턴스화 하면 안됩니다. 대신에, 프레임워크가 자동으로 인스턴스화 하게 하세요.

전역 필터는 기본 필터를 확장할 수 있음
1. HttpAdapter 주입

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

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

  await app.listen(3000);
}
bootstrap();
  1. APP_FILTER
    여기
profile
학습 velog

0개의 댓글