안녕하세요, 백엔드 개발자입니다.
NestJS로 REST API를 개발하다 보면, 하나의 API 엔드포인트를 정의하기 위해 여러 데코레이터를 함께 사용하는 경우가 많습니다.
// 일반적인 NestJS 컨트롤러 메소드
@Post('/')
@ApiOperation({ summary: '새로운 리소스 생성' })
@ApiCreatedResponse({ description: '성공적으로 생성됨' })
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
async createResource(@Body() createDto: CreateResourceDto) {
// ...
}
HTTP 메소드, Swagger 문서, 응답 스펙, 그리고 인증/인가 가드까지. 기능적으로는 명확하지만, API가 많아질수록 이런 코드들은 반복되고 길어집니다. 작은 수정이라도 생기면 여러 곳을 고쳐야 하고, 개발자마다 조금씩 다른 스타일로 작성하여 일관성이 깨지기도 쉽습니다.
이번 글에서는 NestJS의 applyDecorators 유틸리티를 사용하여, 이렇게 반복되는 데코레이터들을 하나의 커스텀 데코레이터로 묶어 코드를 더 깔끔하고 유지보수하기 쉽게 만드는 방법을 알아보겠습니다.
1. 목표: 반복을 줄이고 가독성 높이기
우리의 목표는 아래와 같은 코드를 더 간결하게 만드는 것입니다.
Before (일반적인 방식)
@Post('/')
@ApiOperation({ summary: '리소스 생성' })
@Roles('admin')
async create(...) {
/* ... */
}
After (커스텀 데코레이터 사용)
@ApiRoute({
method: 'POST',
path: '/',
summary: '리소스 생성',
roles: ['admin'],
})
async create(...) {
/* ... */
}
결과적으로 코드는 더 선언적이 되고, API의 명세가 한눈에 들어옵니다.
2. applyDecorators로 데코레이터 조합하기
NestJS는 @nestjs/common 패키지에 applyDecorators라는 유용한 함수를 제공합니다. 이 함수는 말 그대로 여러 데코레이터를 하나로 묶어주는 역할을 합니다.
먼저, 이 기능을 담을 커스텀 데코레이터 파일을 하나 생성합니다.
// src/common/decorators/api-route.decorator.ts
import { applyDecorators } from '@nestjs/common';
import { ApiOperation } from '@nestjs/swagger';
// 1. 데코레이터에 전달할 옵션의 타입을 정의합니다.
export interface ApiRouteOptions {
summary: string;
// ... 앞으로 추가될 다른 옵션들 ...
}
// 2. 커스텀 데코레이터 함수를 만듭니다.
export function ApiRoute(options: ApiRouteOptions) {
// 3. applyDecorators를 사용하여 여러 데코레이터를 조합합니다.
return applyDecorators(
ApiOperation({ summary: options.summary }),
// ... 여기에 다른 데코레이터들을 추가 ...
);
}
3. @ApiRoute 데코레이터 발전시키기
이제 이 데코레이터에 실용적인 기능들을 하나씩 추가해 보겠습니다.
A. HTTP 메소드 동적으로 결정하기
@Post, @Get 등 다양한 HTTP 메소드를 처리하기 위해, 옵션으로 method를 받고 그에 맞는 데코레이터를 반환하도록 만듭니다.
// api-route.decorator.ts
import { applyDecorators, Get, Post, Put, Delete } from '@nestjs/common';
// ...
const MethodMap = {
GET: Get,
POST: Post,
PUT: Put,
DELETE: Delete,
};
export interface ApiRouteOptions {
path: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
summary: string;
// ...
}
export function ApiRoute(options: ApiRouteOptions) {
const { path, method, summary } = options;
const HttpMethodDecorator = MethodMap[method];
return applyDecorators(
HttpMethodDecorator(path), // 1. HTTP 메소드 데코레이터 적용
ApiOperation({ summary }), // 2. Swagger 요약 적용
// ...
);
}
B. 역할 기반 인가 추가하기
@Roles 데코레이터와 @UseGuards를 조합하여, 특정 역할을 가진 사용자만 API를 호출할 수 있도록 만들어 봅시다.
// api-route.decorator.ts
import { applyDecorators, SetMetadata, UseGuards, ... } from '@nestjs/common';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; // 직접 만든 Guard
import { RolesGuard } from 'src/auth/roles.guard'; // 직접 만든 Guard
// @Roles('admin') 데코레이터와 동일한 역할을 하는 커스텀 데코레이터
export const Roles = (roles: string[]) => SetMetadata('roles', roles);
export interface ApiRouteOptions {
// ...
roles?: string[]; // roles 옵션 추가 (선택적)
}
export function ApiRoute(options: ApiRouteOptions) {
// ...
const decorators = [
HttpMethodDecorator(path),
ApiOperation({ summary }),
];
// roles 옵션이 있을 때만 Guard와 Roles 데코레이터를 추가
if (options.roles && options.roles.length > 0) {
decorators.push(
UseGuards(JwtAuthGuard, RolesGuard),
Roles(options.roles),
);
}
return applyDecorators(...decorators);
}
4. 최종 결과 및 사용법
이제 완성된 @ApiRoute 데코레이터를 컨트롤러에서 사용해 봅시다.
// resource.controller.ts
import { Controller, Body } from '@nestjs/common';
import { ApiRoute } from 'src/common/decorators/api-route.decorator';
@Controller('resources')
export class ResourceController {
@ApiRoute({
method: 'POST',
path: '/',
summary: '새로운 리소스를 생성합니다.',
roles: ['admin'], // 관리자만 이 API를 호출할 수 있습니다.
})
async create(@Body() createDto: CreateDto) {
// ...
}
@ApiRoute({
method: 'GET',
path: '/:id',
summary: '특정 리소스를 조회합니다.',
roles: ['user', 'admin'], // 사용자와 관리자 모두 호출 가능합니다.
})
async findOne(@Param('id') id: string) {
// ...
}
@ApiRoute({
method: 'GET',
path: '/',
summary: '모든 리소스를 조회합니다.',
// roles가 없으면 공개 API가 됩니다.
})
async findAll() {
// ...
}
}
마치며
커스텀 데코레이터는 NestJS의 코드를 더 선언적이고 일관성 있게 만들어주는 강력한 도구입니다.
반복되는 데코레이터 조합을 하나로 묶음으로써, 우리는 보일러플레이트 코드를 크게 줄일 수 있을 뿐만 아니라, API의 명세(경로, 메소드, 권한 등)를 하나의 객체로 관리하여 실수를 줄이고 가독성을 높일 수 있습니다. 여러분의 프로젝트에서도 반복되는 패턴이 있다면, 자신만의 데코레이터를 만들어보는 것은 어떨까요?
긴 글 읽어주셔서 감사합니다.
'Backend' 카테고리의 다른 글
| 목록 조회 API, OFFSET 페이지네이션 괜찮을까? (0) | 2025.09.01 |
|---|---|
| 나만의 데코레이터 만들기: 실전 기능 구현 (0) | 2025.08.29 |
| 서버 부하를 줄이는 이미지 업로드 전략 (3편): 유연한 썸네일 URL 관리 패턴 (0) | 2025.08.24 |
| 서버 부하를 줄이는 이미지 업로드 전략 (2편): Multer 스트리밍 파이프라인 구현 (0) | 2025.08.21 |
| 서버 부하를 줄이는 이미지 업로드 전략 (1편): 역할 분리 (0) | 2025.08.19 |
