nest의 공식문서를 토대로 작성합니다.
Nest는 데코레이터라는 언어 기능을 중심으로 구축되었습니다. 데코레이터는 일반적으로 사용되는 많은 프로그래밍 언어에서 잘 알려진 개념이지만 JS 세계에서는 아직 비교적 새로운 개념입니다. 데코레이터의 작동 방식을 더 잘 이해하려면 이 글을 읽어보시기 바랍니다.
- 데코레이터의 간단한 정의
ES2016 데코레이터는 함수를 반환하고 대상, 이름 및 속성 설명자를 인수로 받을 수 있는 표현식입니다. 데코레이터 앞에 @ 문자를 붙여서 데코레이터를 적용하고 데코레이션하려는 항목의 맨 위에 배치하면 됩니다. 데코레이터는 클래스, 메소드 또는 프로포티에 대해 정의할 수 있습니다.
Nest는 HTTP route handler와 함께 사용할 수 있는 유용한 매개변수 데코레이터 세트를 제공합니다. 다음은 제공되는 데코레이터와 이들이 나타내는 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 |
이전에 CatsController
에서 @Body()
랑 @Param()
을 썼던 기억이 나네요~
또한 나만의 맞춤 데코레이터를 만들 수도 있습니다.
node.js
세계에서는 요청 객체에 프로퍼티를 첨부하는 것이 일반적인 관행입니다. 그 후 다음과 같은 코드를 사용하여 각 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);
}
기존에 cats
관련 컨트롤러, 서비스 등을 만들었는데 이제 user
를 사용하네요! user
에 관련된 것들을 새로 만들어 봅시다!
역시 CLI로 간편하게 모듈, 컨트롤러, 서비스를 생성해 봅시다.
$ nest g mo user
$ nest g co user
$ nest g s user
userModule
을 먼저 생성해주셔야 app.module.ts
에 userController
나 userService
가 추가되지 않습니다!
데코레이터의 동작이 특정 조건에 따라 달라지는 경우 데이터 매개 변수를 사용하여 데코레이터의 팩토리 함수에 인수를 전달할 수 있습니다. 이에 대한 한 가지 사용 사례는 요청 객체에서 키별로 속성을 추출하는 사용자 정의 데코레이터입니다. 예를 들어 인증 계층이 요청의 유효성을 검사하고 사용자 엔티티를 요청 객체에 첨부한다고 가정해보겠습니다. 인증된 요청에 대한 사용자 엔티티는 다음과 같을 수 있습니다:
{
"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}`);
}
동일한 데코레이터를 다른 키와 함께 사용하여 다른 프로퍼티에 접근할 수 있습니다. 사용자 객체가 깊거나 복잡한 경우 요청 핸들러 구현을 더 쉽고 가독성 있게 만들 수 있습니다.
HINT
TS 사용자의 경우createParamDecorator<T>()
는 제네릭이라는 점에 유의. 즉createParamDecorator<string>((data, cts) => ...)
와 같이 명시적으로 유형 안전을 적용 가능. 또는 팩토리 함수에서 매개 변수 유형을 지정 가능. 예:createParamDecorator((data: string, ctx) => ...)
. 둘 다 생략하면 데이터 유형은any
가 됨.
저는 직접 해보려고 하드코딩 해봤습니다.
# user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
let request = ctx.switchToHttp().getRequest();
const user = {
id: 101,
firstName: 'Alan',
lastName: 'Turing',
email: 'alan@email.com',
roles: ['admin'],
};
request = { ...request, user };
return data ? request.user?.[data] : request.user;
},
);
# user.controller.ts
import { Controller, Get } from '@nestjs/common';
import { User } from 'src/common/decorator/user.decorator';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get()
async findOne(@User('firstName') firstName: string) {
console.log(`Hello ${firstName}`);
}
}
이렇게 작성하시고 실행해보면
이렇게 콘솔이 잘 찍히는 걸 확인할 수 있습니다.
Nest는 사용자 정의 매개변수 데코레이터를 기본 제공 매개변수(@Body()
, @Param()
, @Query()
)와 동일한 방식으로 처리합니다. 즉 사용자 정의 매개변수에 대해서도 파이프가 실행됩니다. 또한 파이프를 사용자 정의 데코레이터에 직접 적용할 수도 있습니다:
@Get()
async findOne(
@User(new ValidationPipe({ validateCustomDecorators: true }))
user: UserEntity,
) {
console.log(user);
}
HINT
validateCustomDecorators
옵션을true
로 설정해야 함.ValidationPipe
는 기본적으로 사용자 지정 데코레이터로 주석이 달린 인수의 유효성을 검사하지 않음.
저희는 UserEntity
가 없어서 따로 진행하진 않았습니다. 그냥 이렇게 쓰는구나! 정도로 알고 넘어가겠습니다.
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() {}
이렇게 하면 한 번의 선언으로 네 가지 데코레이터를 모두 적용하는 효과가 있습니다.
이 부분도 저희는 없는 게 좀 있어서 그냥 보고 넘어가겠습니다!
WARNING
nestjs/swagger
패키지의@ApiHideProperty()
데코레이터는 컴포저블이 불가능하며applyDecorators
함수에서 제대로 작동 불가.
제 코드입니다. 😎
https://github.com/cxzaqq/cxzaqq-velog/tree/2.10-custom-decorators
고생하셨습니다!
다음 글에서 만나요~~😀
저도 아직 배우는 단계입니다. 지적 감사히 받겠습니다. 함께 열심히 공부해요!!