Node.js/NestJS

[NestJS] 10. 커스텀 데코레이터

턴태 2022. 10. 1. 13:35

커스텀 라우트 데코레이터

Nest는 데코레이터로 불리는 언어 기능으로 구축되었습니다. 데코레이터는 프로그래밍 언어에서 흔히 사용되는 잘 알려진 개념입니다. 그러나 자바스크립트 세계에서는 상대적으로 새롭습니다.

파라미터 데코레이터

Nest는 HTTP 라우트 핸들러와 함께 사용할 수 있는 유용한 파라미터 데코레이터를 제공합니다. 아래의 리스트는 데코레이터와 데코레이터가 표현하는 일반 Express 객체입니다.

 

더보기
@Request(), @Req() req
@Response(), @Res() res
@Next() next
@Session() req.session
@Param(param?: string) req.params / req.params[param]
@Body(param?: string) req.body / req.body[param]
@Query(param?: string) req.query / req.query[param]
@Headers(param?: string) req.headers / req.headers[param]
@Ip() req.ip
@HostParam() req.hosts

추가적으로, 직접 데코레이터를 생성할 수 있습니다.

 

Node.js 세계에서 응답 객체에 프로퍼티를 연결하는 것은 흔한 방법입니다. 그러면, 아래와 같은 코드를 사용하여 각 라우트 핸들러에서 객체의 속성을 추적할 수 있습니다.

const user = req.user;

코드를 조금 더 가독성있고 명확하게 작성하기 위해서 @User() 데코레이터를 생성하고 모든 컨트롤러에 재사용할 수 있습니다.

import { ExecutionContext } from '@nestjs/common';
import { createParamDecorator } from '@nestjs/common/decorators';

export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

 

그후, 원할 때마다 아래와 같이 사용할 수 있습니다.

@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}

데이터 전달

데코레이터의 동작이 몇몇 조건에 의해 달라질 때,  data 매개변수를 사용해 데코레이터의 팩토리 함수에 인수를 전달할 수 있습니다. 이에 대한 사례는 요청 객체를 키를 사용하여 프로퍼티들을 추적하는 사용자 정의 데코레이터입니다. 예를 들어, 인증 계층이 요청이 유효한지 검사하고, 유저 엔티티를 요청 객체에 연결시킨다고 가정해봅시다. 인증된 요청에 대한 유저 엔티티는 다음과 같을 것입니다.

{
  "id": 101,
  "firstName": "Alan",
  "lastName": "Turing",
  "email": "alan@email.com",
  "roles": ["admin"]
}

프로퍼티의 이름을 키로 취하고, 만약 키가 존재한다면 관련된 값을 반환하는 데코레이터를 정의해봅시다(만약 존재하지 않거나, 유저 객체가 생성되지 않았다면 undefined를 반환합니다).

import { ExecutionContext } from '@nestjs/common';
import { createParamDecorator } from '@nestjs/common/decorators';

export const User = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    const user = request.user;

    return data ? user?.[data] : user;
  },
);

컨트롤러에서 @User() 데코레이터를 통해 특정 프로퍼티에 접근하는 방법입니다.

@Get()
async findOne(@User('firstName') firstName: string) {
  console.log(`Hello ${firstName}`);
}

다른 프로퍼티에 대한 다른 키를 통해 이 데코레이터를 사용할 수도 있습니다. 만약 유저 객체가 복잡하다면, 요청 핸들러의 구현을 조금 더 쉽고 가독성있게 만들 수 있습니다.

더보기

타입스크립트 유저의 경우, createParamDecorator<T>()는 일반적인 사용방법입니다. 예를 들어, createParamDecorator<string>((data, ctx) => ...)에서 명시적으로 타입 안전성을 증진시킬 수 있다는 것을 의미합니다. 다른 방법으로는, 팩토리 함수에서 파라미터 타입을 구체적으로 작성할 수도 있습니다(createParamDecorator((data: string, ctx) => ...). 만약 둘을 생략한다면, data 타입은 any가 될 것입니다.

파이프와 함께 사용하기

Nest는 사용자 정의 파라미터 데코레이터를 내장 데코레이터(@Body(), @Param(), @Query())와 같은 방식으로 처리합니다. 즉, 커스텀 파라미터(여기에서는 user 인수)에 대해서도 파이프가 사용됩니다. 게다가, 파이프를 직접 사용자 정의 데코레이터에 적용할 수 있습니다.

@Get()
async findOne(
  @User(new ValidataionPipe({ validateCustomDecorators: true }))
  user: UserEntity,
) {
  console.log(user);
}

validateCustomDecorators 옵션은 반드시 true로 설정되어야 합니다. ValidationPipe는 기본적으로 사용자 정의 데코레이터 인수의 유효성을 검사하지 않기 때문입니다.

데코레이터 구성

Nest는 여러 개의 데코레이터를 구성하는 헬퍼 메서드를 제공합니다. 예를 들어, 인증과 관련된 모든 데코레이터를 하나의 데코레이터에 혼합한다고 가정해봅시다.

 

먼저 @nestjs/swagger를 먼저 설치하겠습니다.

npm i @nestjs/swagger
import {
  applyDecorators,
  SetMetadata,
  UseGuards,
} from '@nestjs/common/decorators';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { AuthGuard } from '../guards/auth.guard';
import { RolesGuard } from '../guards/roles.guard';

export function Auth(...roles: string[]) {
  return applyDecorators(
    SetMetadata('roles', roles),
    UseGuards(AuthGuard, RolesGuard),
    ApiBearerAuth(),
    ApiUnauthorizedResponse({ description: 'Unauthorized' }),
  );
}

이제 이 사용자 정의 @Auth() 데코레이터를 사용할 수 있습니다.

@Get('users')
@Auth('admin')
findAllUsers() {}

한 번의 선언으로 네 개의 데코레이터를 사용하는 효과를 가집니다.