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

© 2026 newgirok

← 글 목록

데코레이터 — 클래스와 메서드에 기능을 추가하는 문법

2025년 11월 27일
TypeScriptDecoratorNestJS메타데이터클래스

NestJS 코드를 보면 @Controller, @Get, @Injectable 같은 @ 문법이 자주 등장합니다. 이것이 데코레이터(Decorator)입니다. 클래스나 메서드 선언 위에 붙여서 기능을 추가하거나 메타데이터를 등록하는 용도로 쓰입니다.

활성화 설정

데코레이터는 실험적 기능으로, tsconfig.json에서 명시적으로 활성화해야 합니다.

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true  // 메타데이터 활용 시 필요
  }
}

experimentalDecorators: TypeScript 5.0 이전의 레거시 데코레이터 API를 사용하는 옵션. NestJS, TypeORM 등 주요 프레임워크는 이 방식을 씁니다.

클래스 데코레이터

클래스 선언 앞에 붙습니다. 클래스 생성자를 인수로 받습니다.

function Sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@Sealed
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

데코레이터 팩토리를 쓰면 인수를 받을 수 있습니다.

function Log(prefix: string) {
  return function (constructor: Function) {
    console.log(`${prefix}: ${constructor.name} 클래스가 정의됨`);
  };
}

@Log("INFO")
class Service {
  // "INFO: Service 클래스가 정의됨" 출력
}

데코레이터 팩토리(Decorator Factory): 데코레이터를 반환하는 함수. @Decorator 형태로 인수를 전달할 수 있습니다.

메서드 데코레이터

메서드 선언 앞에 붙습니다. 메서드의 PropertyDescriptor를 조작할 수 있습니다.

function Measure(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = async function (...args: any[]) {
    const start = Date.now();
    const result = await original.apply(this, args);
    console.log(`${key} 실행 시간: ${Date.now() - start}ms`);
    return result;
  };
  return descriptor;
}

class DataService {
  @Measure
  async fetchData() {
    // 자동으로 실행 시간 측정
  }
}

프로퍼티 데코레이터

프로퍼티 선언 앞에 붙습니다.

function ReadOnly(target: any, key: string) {
  Object.defineProperty(target, key, {
    writable: false,
  });
}

class Config {
  @ReadOnly
  version = "1.0.0";
}

매개변수 데코레이터

메서드의 매개변수 앞에 붙어 메타데이터를 등록합니다. 주로 의존성 주입에서 활용됩니다.

function Body(target: any, methodName: string, paramIndex: number) {
  // 메타데이터에 paramIndex 위치가 request body임을 기록
  Reflect.defineMetadata("body", paramIndex, target, methodName);
}

class UserController {
  createUser(@Body data: CreateUserDto) {
    // data는 요청 본문에서 추출됨
  }
}

NestJS에서의 활용

NestJS는 데코레이터를 통해 라우팅, 의존성 주입, 유효성 검사 등을 선언적으로 처리합니다.

import { Controller, Get, Post, Body, Param } from "@nestjs/common";
import { Injectable } from "@nestjs/common";

@Injectable()
class UserService {
  findAll() {
    return [];
  }
}

@Controller("users")
class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Get(":id")
  findOne(@Param("id") id: string) {
    return { id };
  }

  @Post()
  create(@Body() createUserDto: { name: string }) {
    return createUserDto;
  }
}

@Controller, @Get, @Post, @Body, @Param은 모두 데코레이터입니다. 각 데코레이터가 메타데이터를 등록하고, NestJS 프레임워크가 그 정보를 읽어 라우팅과 의존성 주입을 처리합니다.

데코레이터 적용 순서

여러 데코레이터가 붙으면 아래에서 위로 실행됩니다.

@First
@Second
@Third
class Example {}

// 실행 순서: Third → Second → First

데코레이터 팩토리의 평가(함수 호출)는 위에서 아래로, 실제 데코레이터 적용은 아래에서 위로 처리됩니다.

데코레이터는 코드를 선언적이고 읽기 쉽게 만들지만, 런타임 동작이 코드에서 보이지 않아 디버깅이 어려울 수 있습니다. 프레임워크가 제공하는 데코레이터를 사용하는 것과 직접 작성하는 것은 다른 영역입니다. NestJS처럼 데코레이터 기반 프레임워크를 사용한다면 동작 원리를 이해하는 것이 문제 해결에 도움이 됩니다.

← 이전 글조건부 타입 — 타입 시스템의 if문
다음 글 →선언 파일(.d.ts) — JavaScript 라이브러리에 타입 입히기