NestJS Logo

원문을 읽으며 정리합니다.

Table of Contents

  1. Custom route decorators

Custom route decorators

NestJS는 decorators라고 불리는 language feature를 중심으로 만들어졌다. Decorators는 일반적으로 사용되는 많은 프로그래밍 언어에서 잘 알려진 개념이지만 JavaScript 세계에서는 여전히 상대적으로 새로운 개념이다. decorator가 작동하는 것을 더 잘 이해하기 위해서는, 이 글을 읽어보는 걸 추천한다. 간단한 정의는 다음과 같다.

ES2016 decorator는 함수를 반환하고 target, name, property descriptor를 인자로 갖을 수 있는 expression(표현식)이다. decorator에 @ 문자를 접두사로 붙이고 decorate하려는 무언가의 맨 위에 두어 적용한다. Decorators는 class, method, property에 대해 정의할 수 있다.

Param decorators

NestJS는 HTTP route handler와 함께 사용할 수 있는 유용한 param decorators의 집합을 제공한다. 아래는 제공된 decorators의 목록과 이들이 의미하는 plain Express(or Fastify) object의 목록이다.

@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

추가적으로 custom decorators를 만들 수 있다. 왜 이게 유용할까?

node.js 세계에서, request object에 property를 연결하는 것은 일반적이다. 그런 다음, 다음과 같은 코드를 사용하여 각 route handler에서 수동으로 추출한다.

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

Passing data

decorator의 동작이 일부 조건에 의존한다면, data 파라미터를 사용하여 decorator's factory function(decorator 만드는 공장)에 인자를 전달할 수 있다. 이를 위한 하나의 use case는 request object에서 key에 따라 속성을 추출하는 커스텀 데코레이터 이다. 예를 들어, authentication layer가 요청을 검증하고 request object에 user entity를 부착시킨다고 가정하자. 인증된 요청에 대한 user entity는 다음과 같을 수 있다.

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

데코레이터를 정의해보자. key로 사용하는 property name을 갖고, 만약 그게 존재한다면(또는 undefined, 존재하지 않음, user object 아직 생성 안됨) 관련된 value를 리턴한다.

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

다른 속성을 접근하기 위해 다른 key와 함께 같은 데코레이터를 사용할 수 있다. user object가 deep하거나 complex한 경우, 이렇게 하면 보다 쉽고 가독성 좋게 request handler 구현을 할 수 있다.

TypeScript 사용자라면, createParamDecorator<T>()는 제네릭임을 주의하자. 이 뜻은, 명쾌하고 안전하게 type을 지정할 수 있다. 예를 들어, createParamDecorator<string>((data, ctx) => ...). 또는 factory function 안에서 parameter type을 지정한다. 예를 들어
createParamDecorator((data: string, ctx) => ...). 만약 둘다 생략하면 dataany type이 될 것이다.

Working with pipes

NestJS는 built-in 된 것들(@Body(), @Param(), @Query())과 같은 유형으로 custom param decorators를 다룬다. 즉, custom annotated parameters에도 마찬가지로 pipes가 실행된다(예제에서는 user 인자). 게다가 pipe를 custom decorator에 직접 적용할 수 있다.

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

validateCustomDecorators 옵션은 반드시 true로 설정되어야 한다. ValidationPipe는 디폴트로 custom decorators와 함께 annotated된 arguments를 검증하지 않는다.

Decorator composition

NestJS는 여러가지 decorators를 구성하는 헬퍼 메서드를 제공한다. 예를 들어, 인증과 관련된 모든 데코레이터를 하나의 데코레이터에 결합하기를 원한다고 가정하자. 이 작업은 다음과 같은 구조로 수행할 수 있다.

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

그런 다음, 다음과 같이 custom @Auth() decorator를 사용할 수 있다.

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

이는 하나의 선언으로 4개의 decorators를 모두 적용하는 효과가 있다.

@nestjs/swagger package의 @ApiHideProperty() 데코레이터는 합성할 수 없으며 applyDecorators function에서 적절하게 작동하지 않는다.

Intro NestJS OVERVIEW(0) - Intro