[NestJS] 그 외 기본 개념들
지난 시간 [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 을 살펴보도록 하겠다.