Skip to content

10. Custom decorators


Nest는 데코레이터라는 언어 기능을 중심으로 구축되었다. 데코레이터는 일반적으로 많은 프로그래밍 언어에서 잘 알려진 개념이지만 JavaScript 세계에서는 여전히 비교적 새로운 개념이다.


1. Param decorators

Nest는 HTTP 라우트 핸들러와 함께 사용할 수 있는 유용한 매개변수 데코레이터들을 제공하고, 또한 사용자 정의 데코레이터를 만들 수도 있다.


node.js 세계에서는 요청 객체에 프로퍼티를 첨부하는 것이 일반적이다. 그런 다음 다음과 같은 코드를 사용하여 각 라우트 핸들러에서 수동으로 추출한다.


const user = req.user;


코드를 더 읽기 쉽고 투명하게 만들기 위해 @User() 데코레이터를 만들고 모든 컨트롤러에서 재사용할 수 있다.


user.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

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);
}


2. Passing data

데코레이터의 동작이 일부 조건에 따라 달라지면 data 매개변수를 사용하여 데코레이터의 팩토리 함수에 인수를 전달할 수 있다. 이에 대한 한 가지 사용 사례는 키로 요청 객체에서 프로퍼티를 추출하는 사용자 정의 데코레이터이다. 예를 들어 인증 레이어가 요청의 유효성을 검사하고 사용자 엔터티를 요청 객체에 연결한다고 가정해 보자. 인증된 요청에 대한 사용자 엔터티는 다음과 같다.


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


프로퍼티 이름을 키로 사용하고 관련 값이 있으면 반환하는 데코레이터를 정의해 보자. 이 때 존재하지 않거나 사용자 객체가 생성되지 않은 경우 정의되지 않는다.


user.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";

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}`);
}


다른 키와 함께 이 동일한 데코레이터를 사용하여 다른 프로퍼티에 액세스할 수 있다. 사용자 객체가 깊거나 복잡한 경우 요청 핸들러 구현을 더 쉽고 읽기 쉽게 만들 수 있다.


Note

TypeScript 사용자의 경우, createParamDecorator<T>()는 제네릭이다. 이는 예를 들어 createParamDecorator<string>((data, ctx) => …)과 같이 타입 안전성을 명시적으로 시행할 수 있음을 의미한다. 또는 팩토리 함수에서 createParamDecorator((data: string, ctx) => …)과 같이 매개변수 타입을 지정한다. 둘 다 생략하면 데이터 타입이 any가 된다.


3. Working with pipes

Nest는 내장 매개변수(@Body(), @Param()@Query())와 동일한 방식으로 사용자 정의 매개변수 데코레이터를 처리한다. 즉, 사용자 정의 주석 매개변수에 대해서도 파이프가 실행된다. 또한 파이프를 사용자 정의 데코레이터에 직접 적용할 수 있다.


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


Note

validateCustomDecorators 옵션은 true로 설정해야 한다. ValidationPipe는 기본적으로 사용자 정의 데코레이터로 주석이 달린 인수의 유효성을 검사하지 않는다.


4. Decorator composition

Nest는 여러 데코레이터를 구성하는 헬퍼 메서드를 제공한다. 예를 들어 인증과 관련된 모든 데코레이터를 단일 데코레이터로 결합하려는 경우를 가정한다. 이것은 다음 구성으로 수행할 수 있다.


auth.decorator.ts
import { applyDecorators } from "@nestjs/common";

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


그런 다음 이 사용자 정의 @Auth() 데코레이터를 다음과 같이 사용할 수 있다.


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


이것은 단일 선언으로 4개의 데코레이터를 모두 적용하는 효과가 있다.


References