NestJS Overview - Custom Decorators

Min Su Kwon·2021년 12월 6일
1
post-thumbnail

Custom route decorators

네스트는 데코레이터라는 언어 기능을 활용해서 만들어졌다. 데코레이터는 많은 프로그래밍 언어에서 사용되는 흔한 컨셉이지만, 자바스크립트에는 아직 매우 새로운 개념이다. 데코레이터가 어떻게 동작하는지 좀 더 잘 알아듣기 위해, 이 아티클을 읽어보는 것을 추천한다. 간단하게 정리하면 아래와 같다.

ES2016 데코레이터는 함수를 반환하는 표현식으로, target, name, 그리고 property descriptor를 인자로 받을 수 있다. 데코레이터 앞에 @ 문자를 붙이고 데코레이트 하고자 하는 것의 위에 놓음으로써 적용할 수 있다. 데코레이터는 클래스/메서드/프로퍼티를 위해 정의되고 적용될 수 있다.

Param decorators

네스트가 HTTP 라우트 핸들러와 함께 사용할 수 있는 몇가지 유용한 파라미터 데코레이터를 제공한다. 아래는 데코레이터를 통해 제공되는 Express 또는 Fastify 객체의 목록이다.

  • @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 { 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

만약 데코레이터의 동작이 특정 조건에 의존하고 있다면, data 파라미터를 이용해서 데코레이터의 팩토리 함수에 인자를 넘길 수 있다. 이렇게 사용할 수 있는 유즈케이스 중 하나는 응답 객체로부터 키를 통해 프로퍼티를 추출하는 커스텀 데코레이터다. 우리의 authentication 레이어가 요청을 검증하고 사용자 엔티티를 요청 객체에 붙여준다고 가정하자. 사용자 엔티티는 아래와 같은 모양일 것이다.

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

프로퍼티 이름을 키로 받아서, 관련된 값이 존재한다면 해당 값을 반환하는 데코레이터를 만들어보자.

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

같은 데코레이터를 다른 키 값을 가져오는데 사용할 수 있다. user 객체가 너무 복잡한 구조를 가지고 있다면, 이 데코레이터가 훨씬 쉽고 가독성 좋은 코드를 만드는데 기여할 수 있다.

타입스크립트 사용자들이 주의깊게 봐야하는 것은 createParamDecorator<T>()가 제네릭이라는 것이다. 이는 곧 명시적으로 타입 안정성을 강제할 수 있다는 것을 뜻한다. 예를 들면, createParamDecorator<string>((data, ctx) => ...)과 같이. 다른 방법으로는 팩토리 함수 쪽에서 파라미터 타입을 지정해줄 수도 있다. 예를 들면, createParamDecorator((data: string, ctx) => ...)과 같이. 둘다 생략한다면, data의 타입은 자동으로 any가 된다.

Working with pipes

네스트는 커스텀 파라미터 데코레이들을 빌트인 데코레이터들과 동일하게 취급한다. 이는 곧 파이프들이 커스텀 파라미터를 위해 실행될 수 있음을 의미하며, 추가적으로 커스텀 데코레이터에 다이렉트로 파이프를 적용할 수 도 있다.

validateCustomDecorators 옵션이 true로 설정되어야한다. ValidationPipe는 커스텀 데코레이터에 대해서는 디폴트로 validation을 하지 않는다.

Decorator composition

네스트는 여러 데코레이터를 compose 할 수 있는 헬퍼 메서드를 제공한다. 예를 들어, auth에 관련된 모든 데코레이터를 하나로 합치고 싶다면, 아래와 같은 방법으로 할 수 있다.

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가지 데코레이터를 한꺼번에 적용할 수 있다.

느낀 점

확실히 데코레이터를 활용하면 코드의 가독성이 늘어나고, 어떤 코드인지 표현이 잘 되는 것 같다. 과거에 정말정말 얕게 자바 공부를 할때 잠깐 본 개념이지만, Nest js를 통해서 조금 더 잘 이해하게 된 것 같다. 물론 아직까지 많은 부분에서 사용해보진 못해서, 활용할 수 있는 유즈케이스를 더 찾아봐야겠다.

profile
이제 막 커리어를 시작한 소프트웨어 엔지니어입니다. 배운 것을 정리하면서 조금 더 깊이 이해하려는 습관을 들이려고 합니다. 피드백은 언제나 환영입니다.

0개의 댓글