2023. 6. 7. 19:05ㆍ개발 문서 번역/NestJS
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다.
데코레이터(Decorator, 裝飾器)는 디자인 패턴중 하나로, 일부 프로그래밍 언어에서 이 디자인 패턴을 구현할 수 있습니다.
최근 TypeScript와 Javascript에서도 해당 기능을 추가하였으며, Nest는 데코레이터를 최대한 활용하여 기능을 쉽게 적용할 수 있도록 지원합니다. 개발속도, 가독성을 고려한다면 데코레이터는 유용한 도구가 아닐수 없습니다.
Custom Decorator
Nest는 많은 데코레이터를 지원하는데 특정 상황에선 내장 데코레이터를 사용하여도 해결이 되지 않는 상황이 발생할 수 있습니다. 이를 위해 Nest는 Custom Decorator (커스텀 데코레이터, 自訂裝飾器)기능을 지원합니다.
커스텀 데코레이터의 종류는 3가지로 분류할 수 있습니다.
파라미터 데코레이터
특정 자료들은 내장 데코레이터를 통해 얻어오지 못할 수도 있습니다.
ex: 인증/권한 부여에서 가져온 데이터
Express에 익숙하다면 아래의 코드를 써보신 기억이 있을겁니다.
그렇다면 사용자 정의 데이터가 왜 요청 객체에 저장이 되는걸까요?
이는 Middleware를 통해 확장되어, 해당 인증/권한 부여에선 일반적으로 쓰이는 방법이기 때문입니다.
const user = req.user;
한번 생각해봅시다.
내장 데코레이터를 통해선 어떻게 받아와야 할까요?
@Request 데코레이터를 통해 먼저 요청 객체를 받아오고, 이 요청 객체로부터 값을 받아오면 되는데요.
이런 방식이 나쁘다는건 아닙니다. 하지만 이 동작을 하나의 데코레이터로 지정하여 데코레이터만 붙여
값을 받아올수 있다면 더 편하지 않을까요?
Decorator는 CLI를 통해 생성이 가능합니다.
$ nest generate decorator <DECORATOR_NAME>
주의: <DECORATOR_NAME>은 경로를 포함할 수 있습니다. ex: decorators/user
경로를 포함하여 생성시 src 폴더 안에 경로를 포함하여 Decorator가 생성됩니다.
여기서 User를 decorators 폴더 아래에 생성해보겠습니다.
$ nest generate decorator decorators/user
src 폴더 아래에 decorators 라는 폴더와 함께 user.decorator.ts가 들어있습니다.
생성된 데코레이터의 뼈대는 아래와 같습니다. SetMetadata라는 함수가 하나 보입니다.
import { SetMetadata } from '@nestjs/common';
export const User = (...args: string[]) => SetMetadata('user', args);
그러나 파라미터 데코레이터는 SetMetadata를 사용하지는 않습니다.
createParamDecorator를 사용하며, 이 createParamDecorator를 통해 파라미터 데코레이터를 생성하고
Callback 안에 ExecutionContext를 통해 요청객체를 받아오고, 해당 요청 객체를 통해 원하는 자료를 가져옵니다.
아래는 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;
},
);
User 데코레이터를 완성되었습니다. Middleware 하나를 더 만들어 user를 요청 객체에 추가하도록 하겠습니다.
위와 같이 CLI를 통해 생성해보겠습니다.
$ nest generate middleware middlewares/add-user
add-user.middleware.ts의 내용을 아래와 같이 수정해봅시다.
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class AddUserMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.user = { name: 'WOO' };
next();
}
}
다음으로 AppModule에서 AddUserMiddleware를 적용시켜봅시다.
이렇게되면 user를 요청 객체에 담을 수 있게 됩니다.
다음으로 User 데코레이터를 통해 user의 내용을 받아옴과 동시에 클라이언트에게 넘겨주도록 하겠습니다.
아래는 app.controller.ts의 예제입니다.
import { Controller, Get } from '@nestjs/common';
import { User } from './decorators/user.decorator';
@Controller()
export class AppController {
constructor() {}
@Get()
getHello(@User() user: any): string {
return user;
}
}
http://127.0.0.1:3000 에 접속하면 아래와 같은 결과를 확인할 수 있습니다.
@Param('id')와 같이 특정 자료만 가져오고 싶다면 어떻게 설계해야 할까요?
createParamDecorator 안의 Callback에 ExecutionContext 외에 data라는게 존재합니다.
사실 이 data는 데코레이터 안의 파라미터이므로, 해당 data를 통해 사용자 데이터를 추출해야 합니다.
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;
},
);
app.controller.ts를 통해 user안의 name을 얻어오겠습니다.
import { Controller, Get } from '@nestjs/common';
import { User } from './decorators/user.decorator';
@Controller()
export class AppController {
constructor() {}
@Get()
getHello(@User('name') name: string): string {
return name;
}
}
http://127.0.0.1:3000 에 접속하면 아래와 같은 결과를 확인할 수 있습니다.
커스텀 Metadata 데코레이터
가끔은 일부 메서드에 대한 Metadata를 설정해야 할 때가 있을겁니다.
이때 Metadata를 설정하여 특정 역할만이 해당 메서드에 접근할수 있도록 할 수 있습니다.
간단한 역할 기반 제어기능을 통해 어떤 뜻인지 알아봅시다.
CLI를 통해 Roles를 생성할 수 있습니다.
$ nest generate decorator decoractors/roles
생성된 뼈대가 바로 커스텀 MetaData 데코레이터의 양식입니다.
SetMetadata가 바로 MetaData 데코레이터 입니다.
import { SetMetadata } from '@nestjs/common';
export const Roles = (...args: string[]) => SetMetadata('roles', args);
이 예제의 의미는 다음과 같습니다.
Rols를 데코레이터이며, @Roles('admin')을 사용하여
admin 문자열을 데코레이터에 전달한 후 SetMetadata를 사용하여
roles를 key 값으로 설정하고 ['admin']을 해당 값으로 지정하여 Metadata를 설정합니다.
다음으로 RoleGuard를 설정하여 역할 기반 제어를 모방해보겠습니다.
$ nest generate guard guards/role
Metadata의 값을 얻어오기 위해선 반드시 Nest가 제공하는 Reflector를 사용해야 합니다.
의존성 주입을 사용하여 Reflector를 가져오고
get(metadataKey: any, target: Function | Type<any>)로 가져올 Metadata를 지정합니다. 이중 metadataKey는 지정할 Metadata Key를 getHandler에는 target을 입력하면 됩니다.
role.guard.ts의 내용을 수정해보겠습니다.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
const request = context.switchToHttp().getRequest();
const user = request.user;
return this.matchRoles(roles, user.roles);
}
private matchRoles(resources: string[], target: string[]): boolean {
return !!resources.find(x => target.find(y => y === x));
}
}
Roles와 RoleGuard의 정의가 완료되었습니다.
마지막으로 AddUserMiddleware의 내용을 수정하여 staff와 user를 추가해봅시다.
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class AddUserMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.user = { name: 'WOO', roles: ['staff'] };
next();
}
}
마지막으로 app.controller.ts를 수정하여 getHello는 admin의 권한이 있는 사람만 엑세스 할 수 있도록 설정했습니다.
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles } from './decorators/roles/roles.decorator';
import { User } from './decorators/user/user.decorator';
import { RoleGuard } from './guards/role/role.guard';
@Controller()
export class AppController {
constructor() {}
@UseGuards(RoleGuard)
@Roles('admin')
@Get()
getHello(@User('name') name: string): string {
return name;
}
}
http://127.0.0.1:3000에 접속해보면...
위와 같이 값을 받아올수 없게됩니다. (RoleGuard에 막힌 모습입니다.)
데코레이터 합쳐 사용하기
일부 데코레이터는 서로 관련이 있을 수 있습니다.
ex: 인증을 위해 가드를 사용하고 사용자 Metadata를 추가해야 할 경우
이 경우 각각의 데코레이터를 반복적으로 사용하는 대신
Nest에서는 applyDecorators라는 함수로
여러 데코레이터를 묶어 하나의 데코레이터로 통합하는 기능을 제공합니다.
각각 기능을 구현할 때마다 해당 합쳐진 데코레이터를 사용하면 됩니다. 아래에 인증/권한 검사를 진행하는 간단한 통합 데코레이터를 만들어 보겠습니다.
먼저 CLI를 통해 Auth Decorator를 생성합니다.
$ nest generate decorators/auth
주의: 이번 포스팅은 데코레이터를 합치는 기능에 초점을 두기 위해
AuthGuard의 내용은 수정하지 않습니다. true만 반환하도록 설정 하면 되니까요.
AuthGuard를 생성한 후에 auth.decorator.ts를 수정하겠습니다.
applyDecorators로 UseGuards와 Roles 를 합쳐 하나의 데코레이터로 사용하겠습니다.
import { applyDecorators, UseGuards } from '@nestjs/common';
import { Roles } from '../roles/roles.decorator';
import {AuthGuard} from '../../guards/auth/auth.guard';
import {RoleGuard} from '../../guards/role/role.guard';
export const Auth = (...roles: string[]) => applyDecorators(
Roles(...roles),
UseGuards(AuthGuard, RoleGuard)
);
마지막으로 app.controller.ts를 수정합니다.
Auth 데코레이터를 적용하여 getHello 메서드는 staff만이 접근가능하게 설정하겠습니다.
마치며
Custom Decorator는 Nest가 지원하는 내장 데코레이터의 부족한 부분을 채워줄 수 있으며
매우 유연한것이 특징입니다.
아래는 오늘 배운 내용의 요약본입니다.
1. Custom Decorator는 : 파라미터 데코레이터, 커스텀 Metadata 데코레이터, 통합 데코레이터의 구현이 가능합니다.
2. 파라미터 데코레이터는 createParamDecorator를 통해 만들 수 있습니다.
3. 커스텀 MetaData 데코레이터는 SetMetadata를 통해 확장이 가능합니다.
4. 통합 데코레이터는 applyDecorators를 통해 만들 수 있습니다.
Nest의 기본 기능과 사용 방식에 대해 소개를 마쳤습니다.
다음 포스팅에서부터는 심화 기능에 대해 배워 볼겁니다. 기대해주세요!
'개발 문서 번역 > NestJS' 카테고리의 다른 글
NestJS 帶你飛! 시리즈 번역 16# Configuration (0) | 2023.06.10 |
---|---|
NestJS 帶你飛! 시리즈 번역 15# Dynamic Module (0) | 2023.06.09 |
NestJS 帶你飛! 시리즈 번역 13# Guard (0) | 2023.06.06 |
NestJS 帶你飛! 시리즈 번역 12# Interceptor (0) | 2023.06.05 |
NestJS 帶你飛! 시리즈 번역 11# Middleware (0) | 2023.06.04 |