NestJS 코드를 처음 보면 클래스 위에 @Controller, @Injectable 같은 문법이 가득하다. 이것들은 단순한 주석이 아니라 런타임에 동작하는 함수다. TypeScript 데코레이터가 어떻게 NestJS의 의존성 주입과 라우팅을 가능하게 하는지 살펴본다.
데코레이터는 클래스, 메서드, 프로퍼티, 파라미터에 메타데이터를 첨부하는 함수다. @ 기호 뒤에 함수 이름을 붙여 선언하며, 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('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()
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 컨테이너를 구성한다. 런타임에는 이미 모든 연결이 완료된 상태라 별도의 리플렉션 비용 없이 동작한다.