개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

NestJS Decorator — 클래스와 메서드에 메타데이터를 추가하는 문법

2025년 3월 1일
NestJSDecorator@Controller@InjectableMetadata

NestJS 코드를 처음 보면 클래스 위에 @Controller, @Injectable 같은 문법이 가득하다. 이것들은 단순한 주석이 아니라 런타임에 동작하는 함수다. TypeScript 데코레이터가 어떻게 NestJS의 의존성 주입과 라우팅을 가능하게 하는지 살펴본다.

TypeScript 데코레이터란

데코레이터는 클래스, 메서드, 프로퍼티, 파라미터에 메타데이터를 첨부하는 함수다. @ 기호 뒤에 함수 이름을 붙여 선언하며, TypeScript 컴파일러가 tsconfig.json의 experimentalDecorators: true 옵션을 통해 처리한다.

클래스 정의 시점
      │
      ▼
 @Injectable()  ──▶  Reflect.defineMetadata('injectable', true, Target)
      │
      ▼
 NestJS IoC Container
      │
      ├── 생성자 파라미터 타입 읽기 (reflect-metadata)
      └── 인스턴스 생성 후 의존성 주입

IoC(Inversion of Control): 객체 생성과 의존성 연결을 프레임워크가 담당하는 패턴.

핵심 데코레이터 목록

데코레이터대상역할
@Module클래스모듈 메타데이터 등록
@Controller클래스HTTP 라우트 prefix 지정
@Injectable클래스IoC 컨테이너에 Provider로 등록
@Get / @Post메서드HTTP 메서드와 경로 바인딩
@Body파라미터request body 추출
@Param파라미터URL 경로 파라미터 추출
@Query파라미터Query string 추출

@Controller와 @Get 동작 방식

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id);
  }

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
}

@Controller('users')는 클래스에 path: 'users' 메타데이터를 붙인다. @Get(':id')는 메서드에 method: GET, path: ':id'를 붙인다. NestJS는 앱 초기화 시 이 메타데이터를 읽어 Express/Fastify 라우터에 자동으로 등록한다.

reflect-metadata: TypeScript 데코레이터가 메타데이터를 읽고 쓸 수 있도록 하는 폴리필 라이브러리.

@Injectable과 의존성 주입

@Injectable()
export class UsersService {
  constructor(private readonly repo: UsersRepository) {}
}

@Injectable을 붙이면 NestJS IoC 컨테이너가 이 클래스를 Provider로 인식한다. 생성자 파라미터의 타입 정보는 reflect-metadata를 통해 런타임에 읽히며, 컨테이너가 알아서 UsersRepository 인스턴스를 주입한다.

커스텀 데코레이터 작성

반복되는 파라미터 추출 로직은 커스텀 데코레이터로 묶을 수 있다.

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

이후 컨트롤러에서 @CurrentUser user: User처럼 사용한다. createParamDecorator는 내부적으로 Reflect.defineMetadata를 호출해 파라미터 인덱스와 팩토리 함수를 저장한다.

@CurrentUser()
      │
      ▼
 createParamDecorator 팩토리
      │
      ▼
 ExecutionContext에서 request.user 추출
      │
      ▼
 메서드 파라미터로 전달

메타데이터 흐름 요약

데코레이터는 클래스 정의 시점에 실행되어 메타데이터를 저장하고, NestJS는 앱 부트스트랩 시점에 그 메타데이터를 읽어 라우터와 DI 컨테이너를 구성한다. 런타임에는 이미 모든 연결이 완료된 상태라 별도의 리플렉션 비용 없이 동작한다.

← 이전 글NestJS Dependency Injection — 의존성을 자동으로 주입하는 방식
다음 글 →NestJS DTO — 계층 간 데이터를 전달하는 객체