본문으로 바로가기
2222

나만의 데코레이터 만들기: 실전 기능 구현

category Backend 2025. 8. 29. 09:57

안녕하세요, 백엔드 개발자입니다.

지난 1편에서는 applyDecorators를 사용하여 여러 데코레이터를 하나로 묶는 기본적인 방법에 대해 알아보았습니다.

이번 2편에서는 1편에서 다룬 기본 개념을 확장하여, 실제 프로젝트에서 사용하는 @ApiRoute 데코레이터의 전체 코드를 살펴보며 어떤 실용적인 기능들을 추가할 수 있는지 자세히 분석해 보겠습니다.

인증, 파일 업로드, API 문서화 등 데코레이터 하나로 처리하는 과정을 보실 수 있습니다.

1. 우리의 설계도: ApiRouteOptions 인터페이스

좋은 데코레이터는 사용하는 사람이 어떤 옵션을 사용할 수 있는지 명확하게 알려주는 것에서 시작합니다. @ApiRoute가 처리할 모든 기능을 인터페이스로 먼저 정의합니다.

// decorators/route/index.ts

export interface ApiRouteOptions {
  path: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  summary?: string;
  description?: string;
  roles?: string[]; // 'user', 'admin' 등 역할 기반 제어
  version?: string; // API 버전 관리
  exclude?: boolean; // Swagger 문서에서 제외
  multerConfig?: MobidicMulterConfig; // 파일 업로드 설정
  auth?: boolean; // Swagger의 자물쇠 아이콘 표시 여부
}

이 인터페이스만 봐도, 우리 데코레이터가 라우팅, 문서화, 인가, 파일 업로드 등 다양한 기능을 처리할 것임을 짐작할 수 있습니다.

2. 핵심 구조: 데코레이터 배열과 조건부 로직

@ApiRoute의 핵심은 하나의 큰 데코레이터 배열을 만들고, options 객체에 들어온 값에 따라 필요한 데코레이터를 이 배열에 조건부로 추가(push)하는 것입니다.

// decorators/route/index.ts

import { applyDecorators, ... } from '@nestjs/common';
// ... 수많은 데코레이터 import

export function ApiRoute(options: ApiRouteOptions) {
  const decorators = []; // 빈 데코레이터 배열로 시작

  // 1. HTTP 메소드 데코레이터를 추가
  if (options.method === 'GET') {
    decorators.push(Get(options.path));
  } else if (options.method === 'POST') {
    // ...
  }

  // 2. Swagger 관련 데코레이터를 추가
  if (options.summary) {
    decorators.push(ApiOperation({ summary: options.summary }));
  }

  // 3. 역할(Roles) 기반 인가 데코레이터를 추가
  if (options.roles) {
    decorators.push(SetMetadata('roles', options.roles));
    decorators.push(UseGuards(JwtAuthGuard, UserRoleGuard));
    decorators.push(ApiBearerAuth());
  }

  // ... 필요한 모든 로직을 거친 후 ...

  // 4. 배열에 담긴 모든 데코레이터를 최종적으로 적용
  return applyDecorators(...decorators);
}

3. 기능별 상세 구현

이제 index.ts 코드에 담긴 실제 기능들을 하나씩 살펴보겠습니다.

A. 인증과 인가 한번에 처리하기 (roles)

roles 옵션이 주어지면, 단순히 @Roles 메타데이터만 추가하는 것이 아니라, 실제 인증/인가를 처리하는 Guard들과 Swagger 문서에 필요한 데코레이터까지 한번에 적용합니다.

// decorators/route/index.ts 의 일부

if (options.roles) {
  // Swagger에 401, 403 응답 예시를 자동으로 추가
  decorators.push(ApiResponse({ status: 401, description: 'Unauthorized' }));
  decorators.push(ApiResponse({ status: 403, description: 'AccessNotAllow' }));

  // RolesGuard가 읽을 수 있도록 메타데이터 설정
  decorators.push(SetMetadata('roles', options.roles));

  // 실제 인증/인가를 수행할 Guard 적용
  // JwtAuthGuard로 1차 인증, User/AdminRoleGuard로 2차 인가
  decorators.push(UseGuards(JwtAuthGuard, UserRoleGuard /* or AdminRoleGuard */));

  // Swagger UI에 자물쇠 아이콘을 표시하여 인증이 필요함을 명시
  decorators.push(ApiBearerAuth());
}

B. 파일 업로드까지 품기 (multerConfig)

multerConfig 옵션이 있다면, 해당 API가 파일 업로드를 처리한다는 것을 의미합니다. 이 경우, Content-Type을 지정하고 FileInterceptor를 적용하는 로직이 실행됩니다.

// decorators/route/index.ts 의 일부

if (options.multerConfig) {
  // 1. 이 API가 multipart/form-data를 소비한다고 Swagger에 명시
  decorators.push(ApiConsumes('multipart/form-data'));

  // 2. multer 설정을 생성
  const multerOptions = MobidicMulterOptions(options.multerConfig);

  // 3. 파일이 여러 개인지 단일 파일인지에 따라 적절한 인터셉터 적용
  if (options.multerConfig.multiple) {
    decorators.push(UseInterceptors(FilesInterceptor(fieldName, maxCount, multerOptions)));
  } else {
    decorators.push(UseInterceptors(FileInterceptor(fieldName, multerOptions)));
  }
} else {
  // 파일 업로드가 아니라면, 일반적인 Content-Type을 명시
  decorators.push(ApiConsumes('application/json'));
}

C. 일관성 있는 API 응답 (ApiResponse)

모든 API에 대해 성공/실패 응답을 일일이 작성하는 것은 번거로운 일입니다. @ApiRoute는 기본적인 성공(200/201)과 서버 에러(500) 응답을 자동으로 추가하여 일관성을 유지합니다.

// decorators/route/index.ts 의 일부

if (['POST', 'PUT'].includes(method)) {
  decorators.push(ApiResponse({ status: 201, description: 'Success' }));
} else {
  decorators.push(ApiResponse({ status: 200, description: 'Success' }));
}
decorators.push(ApiResponse({ status: 500, description: 'Error' }));

마치며

이번 2편에서는 실제 프로젝트의 코드를 바탕으로, 단순한 데코레이터 조합을 넘어 인증, 파일 업로드, API 문서화 등 복잡하고 실용적인 기능들을 어떻게 하나의 커스텀 데코레이터로 추상화할 수 있는지 살펴보았습니다.

이렇게 잘 만들어진 데코레이터는 팀의 생산성을 높이고, 실수를 줄이며, 전체 API의 일관성을 유지할 수 있었습니다.

긴 글 읽어주셔서 감사합니다.