Custom route Decorators
Custom route decorators
Nest decorator로 불리는 language feature로 build 됬다. Decorator는 programming language에서 흔히 사용되는 개념이다. 하지만 Javascript 세상에서는, 여전히 상대적으로 새로운 개념이다. 어떻게 decorator가 작동하는지 더 이해하기 위해서는, 다음을 기사를 추천한다.
ES2016 decorator는 함수를 반환하는 표현식이며, 대상, 이름, 속성 기술자를 인수로 받을 수 있다. 데코레이터를 적용하려면 데코레이터 앞에 '@' 문자를 붙이고 데코레이팅하려는 것의 맨 위에 배치한다. decorator는 class, method 또는 property를 위해 정의될 수 있다.
Param decorators
Nest는 HTTP route handler와 함께 사용할 수 있는 유용한 param decorator를 제공한다. 아래는 제공되는 Express, Fastify 객체를 쉽게 다를 수 있게 해주는 데코레이터 리스트이다.
@Decorator | object | description |
---|---|---|
@Reqeust(), @Req() | req | 전체 요청 객체(req)를 주입한다. |
@Response(), @Res() | res | 전체 응답 객체(res)를 주입한다. |
@Next() | next | next 함수를 주입한다. 미들웨어 함수에서 주로 사용된다. |
@Session() | req.session | 세션 객체를 주입한다. |
@Param(param?: string) | req.params/ req.params[param] | URL 파라미터를 주입한다. param 인자를 제공하면 해당 파라미터의 값을 가져오고, 제공하지 않으면 모든 파라미터를 객체 형태로 가져온다 |
@Body(param?:string) | req.body/req.body[param] | 요청 본문(body)을 주입한다. param 인자를 제공하면 해당 본문 필드의 값을 가져오고, 제공하지 않으면 본문을 객체 형태로 가져온다. |
@Query(param?:string) | req.query/req.query[param] | 쿼리 문자열을 주입한다. param 인자를 제공하면 해당 쿼리 파라미터의 값을 가져오고, 제공하지 않으면 모든 쿼리 파라미터를 객체 형태로 가져온다. |
@Headers(param?:string) | req.headers/req.headers[param] | 요청 헤더를 주입한다. param 인자를 제공하면 해당 헤더의 값을 가져오고, 제공하지 않으면 모든 헤드를 객체 형태로 가져온다. |
@Ip() | req.ip | 요청의 IP주소를 주입한다. |
@Query | req.hosts | 호스트 파라미터를 주입한다. 이는 요청의 호스트 정보를 가져온다. |
추가적으로, custom decorators를 만들 수 있다.
node.js에서는 속성(properties)를 request object에 붙이는게 흔한 관행이다. 이런 속성을 route handler에서 extract할 수 있다.
const user = req.user;
code를 좀 더 readable하고 transparent하게 만들기 위해, @User() decorator를 만들고, 이를 controller 전반에 걸쳐 재사용할 수 있다.
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' 매개변수를 사용하여 데코레이터의 팩토리 함수에 인수를 전달할 수 있다. custom decorator를 사용하면 request object에서 특정 key에 해당하는 속성을 추출하여 route handler method에 주입할 수 있다. 이를 통해 코드의 가독성과 재사용성을 높일 수 있다. 예를 들어 authentication layer request의 유효성을 검사한다. 그리고 user entity에 request object를 붙인다. authenticated request의 user entity는 다음과 같을 것 이다.
{
"id": 101,
"firstName": "Alan",
"lastName": "Turing",
"email": "alan@email.com",
"roles": ["admin"]
}
name을 key로 갖는 decorator를 정의해보자. 이는 만일 존재한다면 관련된 value를 반환한다.(만일 존재하지 않거나 user object가 만들어져 있지 않다면 undefined를 반환한다.)
// 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;
// user에서 data에 해당하는 정보가 있다면 그것을 반환
// 그렇지 data가 지정되지 않았다면 user 객체를 반환
return data ? user?.[data] : user;
},
);
// 사용
import { Controller, Get } from '@nestjs/common';
import { User } from './user.decorator'; // 경로는 실제 파일 위치에 따라 다릅니다.
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@User() user: any) {
// 전체 사용자 객체 반환
console.log(user);
return user;
}
@Get('email')
getUserEmail(@User('email') email: string) {
// 사용자 이름만 반환
console.log(email);
return email;
}
}
controller에서 @User() decorator를 통해 어떻게 특정한 property에 접근하는가?
@Get()
async findOne(@User('firstName') firstName: string) {
console.log(`Hello ${firstName}`);
}
위의 같은 decorator, 허나 다른 key로 다른 property에 접근할 수 있다.
사용자 객체가 깊거나 복잡한 경우, 이 방법을 통해 request handler 구현을 더 쉽게 만들고 가독성을 높일 수 있다.
<팁!>
Typescript 사용자는 createParamDecorator
factory function: 객체를 생성하는 함수. 객체의 생성과 초기화를 담당하며, 동일한 함수로 다양한 형태의 객체를 만들 수 있는 유연성을 제공한다.
Working with pipes
Nest는 기본 내장된 @Body(), @Param(), @Query()에서 처리하는 것과 같은 방식으로 custom param decorator를 처리한다.
이는 pipe는 custom annotated parameter에서도 실행된다는 걸 의미한다. (e.g. 위의 user argument ) 게다가, pipe를 바로 custom decorator에 apply할 수 있다. 그냥 custom decorator에도 pipe가 적용된다는 걸 뜻한다.
@Get()
async findOne(
@User(new ValidationPipe({ validateCustomDecorators: true }))
user: UserEntity,
) {
console.log(user);
}
<팁!>
validateCustomDecorator 옵션은 반드시 true로 설정되어야 한다. ValidationPipe는 default로 custom decorator로 주석이 달린 인수를 검증하지 않는다.
Decorator composition
Nest는 mutiple decorator를 구성할 수 있도록 helper method를 제공한다. 예를 들어, authentication과 관련된 모든 decorator를 하나의 decorator로 combine하기를 원한다고 하자. 이는 다음과 같은 구성으로 완료된다.
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
이렇다면 다음과 같이 @Auth()decorator를 사용할 수 있다.
@Get('users')
@Auth('admin')
findAllUsers() {}
이렇게 함으로써 4개의 decorator를 하나의 선언으로 완료할 수 있다
<주의사항!>
@nestjs/swagger에서의 @ApiHidePropety() decorator는 조합할 수 없으며, applyDecorators 함수와 함께 제대로 작동하지 않는다.