[NestJS] 그 외 기본 개념들

JeungJoo Lee
CrocusEnergy
Published in
11 min readSep 17, 2020

지난 시간 [NestJS] Middleware 개념 및 실습 을 살펴보았다. 오늘은 그 외 NestJS 의 개념들을 살펴보도록 하겠다.

그 외 기본 개념들

그 외 Exception Filters, Pipes 그리고 Custom decorators 등의 다양한 기본 개념들이 존재하는데 간단히 집고 넘어 가도록 하겠다.

이 외 Guards나 Interceptor 의 개념은 간단히 Gurads 는 Role에 따라 Route의 수행 여부를 권한 별로 설정할 수 있는 기능이고 Interceptor 는 Request 에서 Response 라이프 사이클에서 특정 함수가 개입하여 처리 및 데이터 조작 또는 로깅 등 다양한 기능을 이룰 수 있는 기능으로 근본적인 동작원리는 Filter 와 다르지만 유사성이 존재한다고 볼 수 있다.

Exception Filters

모든 어플리케이션에서는 Exception 에러 처리가 중요하다. 어떤 기능이든지 에러가 없다는 보장은 없고 그 에러를 어떻게 잘 핸들링 할 수 있을까에 대한 문제가 있다. 우선 except 폴더를 만들어 내장된 Http Exception 에러를 발생 시켜 보도록 하겠다.

import { Controller, Get, HttpException, HttpStatus, Param } from '@nestjs/common';

@Controller('except')
export class ExceptController {
@Get(':code')
executeError(@Param('code') code: HttpStatus) {
if (code >= 200 && code < 400) {
return 'status ok';
} else if (code == 403) {
**throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'Forbidden Error ',
}, HttpStatus.FORBIDDEN)**
}
}
}

우선 403 을 path variable 로 담아서 줬을 때 컨트롤러를 통해 code가 403일 경우 HttpException 을 throw 하도록 코드를 작성하였고 이 throw 를 했을 때 기본적으로 어떠한 메시지를 커스텀하게 담을 수 있는지도 코드로 작성 해보았다. Nest 의 경우 HttpException 을 base로 상속하여 기본적으로 제공하는 Exception 종류 들이 많은데 아래와 같다.

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

위의 HttpException 을 토대로 throw 한 에러를 공통으로 처리할 수 있는 ExceptionFilter도 작성하여 데코레이터를 해당 ExceptionFilter의 객체를 주입하므로 response 체계를 공통화 시킬 수 있는데 코드는 아래와 같다.

src/exception/HttpExceptionFilter.ts

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, Injectable } 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,
});
}
}

Catch 데코레이터를 통해서 HttpException 에 관련된 에러를 Catch 하겠다 라는 선언이다. ExceptionFilter 의 인터페이스를 implements 하고 있고 catch 함수를 구현 하면 되는데 코드는 response 객체를 통해 statusCode, timestamp, 에러가 발생한 path 를 출력하고자 한다.

HttpExceptionFilter를 사용하고자 하는 컨트롤러의 Route 에서 UseFilters 라는 데코레이터를 이용하여 사용할 수 있다

@Get(':code')
**@UseFilters(new HttpExceptionFilter())**
executeError(@Param('code') code: HttpStatus) {
if (code >= 200 && code < 400) {
return 'status ok';
} else if (code == 403) {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'Forbidden Error ',
}, HttpStatus.FORBIDDEN)
}
}

위에 처럼 사용하게 되면 throw 된 HttpException 에 관련된 모든 에러들은 HttpExceptionFilter 에서 처리되게 된다. 403으로 줬을 때 결과는 아래와 같다. 이런식으로 실제 각 Exception 별로 에러를 공통화 시킬 수 있으며 개발시 유용하게 사용될 여지가 많은 기능 들이다.

그렇다면, 이렇게 일일이 다 각 Route 정보 위에 데코레이터인 UseFilters 를 선언하면서 선택적으로 사용해야만 하느냐라는 질문이 생길 것이다. 아래와 같이 모든 어플리케이션 레벨에서 공통화 시킬 수 있는 방법이 있는데 적용해보자

src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './exception/HttpExceptionFilter';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {});
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();

App의 Entry 포인트인 main.ts 에서 NestFactory.create 를 통해 app 객체가 생성되는 곳에서 useGlobalFilters 함수를 통해 등록하여 사용할 수 있는데 이때 위와 같이 선언하면 모든 Route 의 HttpException 에러에 대한 공통 처리 로직이 반영된다.

위에 Exception 의 경우 HttpException 에 관련된 에러만 설정한 것인데 전체를 적용하기 위해서는 @Catch 데코레이터 에 HttpException 을 빼면 전체에 대한 Exception Handler 로써 catch 가 될 것이다.

이 외에도 APP_FILTER 를 통해 모듈 단위로 선별적으로 처리를 할 수 있는 방법이 존재하는데 일단은 넘어가도록 하겠다.

Pipes

파이프는 간단하게 말해 HttpRequest 로 넘어온 파라미터나 쿼리 또는 Body 를 Validation 해주거나 원시타입을 조작하거나 원하는 타입이 넘어오지 못했을 때 에러를 발생시킬 수 있는 기능을 담당한다. 간단하게 보자면 코드는 아래와 같다.

path variable 로 넘어온 id 는 number 타입이어야한다. 만약 string 이나 number가 아닌 다른 타입이 넘어왔을 때는 HttpStatus를 NOT_ACCEPTABLE 로 Response 할 수 있다.

@Get('pipe/:id')
exPipes(
@Param(
'id',
new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }),
)
id: number,
) {
return `입력 받은 Number : ${id}`;
}

만약 abc 라는 문자열로 path 를 넘겼을 때 모습은 아래와 같다.

만약 숫자형으로 제대로 넘긴다면 출력이 잘 되는 것을 아래에서 확인할 수 있다.

이 외 POST 메서드로 처리할 때 데이터를 Payload 에 담아 처리하는 과정이 있을 것이라 예상된다. 이 때 서버사이드 쪽에서 Pipe 기능을 이용해 Validation 할 수 있는 코드를 만들어 적용 시킬 수 있다.

커스텀 Decorator 만들어보기

지금까지는 대부분 NestJS 에서 제공하는 기본 데코레이터를 사용하여 보았다. 만약 이 데코레이터를 사용자가 직접 만들어서 사용할 일이 있을 수도 있을텐데 그런 경우 어떻게 만들 수 있는지 아래 코드에서 확인해보도록 하자.

우선 아래처럼 AuthToken이라는 데코레이터를 만들었다. 하는 역할은 Request 헤더 정보에서 auth_token 으로 되어있는 토큰을 가져오는 공통 함수이다. 만약 header 정보에 토큰이 없다면 NOT DEFINED 메시지를 주도록 설정하였다.

src/common/decorators/common.authToken.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';export const AuthToken = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const { headers } = request;
console.log(headers);
return headers['auth_token'] ? headers['auth_token'] : '"NOT DEFINED"';
}
)

위에서 만든 데코레이터를 사용해보자.

src/exception/except.controller.ts

@Get('auth/token')
getAuthToken(
@AuthToken() token: string
) {
return `HEADER에 입력한 토근은 ${token} 입니다.`
}

AuthToken 데코레이터를 통해 추출된 헤더의 토큰 정보 중 auth_token 은 token 변수에 string 타입으로 바인딩 될 것이다. 그리고 출력하면 아래와 같이 입력된 HEADER 의 auth_token 의 값이 그대로 출력되는 것을 확인할 수 있다.

위의 과정은 단순하게 데코레이터를 만들긴 했지만 좀 더 상황에 따라 복잡한 데코레이터를 유용하게 만들어 공통화 시키는 작업이 있을 수 있을 것이다.

이상 NestJS 의 기본적인 개념을 살펴보았고 다음시간에는 NestJS 프로젝트에서 지원하는 라이브러리는 아니지만 NodeJS ORM Framework 중 하나인 TypeORM 을 살펴보도록 하겠다.

다음. [NestJS] TypeORM 기본 CRUD 작성하기

--

--