NestJS를 처음 접하면 @Injectable, @Inject 같은 데코레이터가 낯설게 느껴진다. 하지만 이 데코레이터들이 바로 NestJS의 핵심인 Dependency Injection 시스템을 구성하는 요소다. DI를 이해하면 왜 NestJS 코드가 그런 구조를 갖는지 자연스럽게 납득하게 된다.
전통적인 방식에서는 클래스 내부에서 직접 new 키워드로 의존 객체를 생성한다.
// 기존 방식 — 강한 결합
class OrderService {
private userService = new UserService(); // 직접 생성
}
이 방식은 OrderService가 UserService의 생성 방식까지 알고 있어야 한다. UserService의 생성자 시그니처가 바뀌면 OrderService도 함께 수정해야 한다.
Dependency Injection은 이 책임을 외부로 넘긴다. 클래스는 "나는 이 타입이 필요하다"고 선언만 하고, 실제 인스턴스 생성은 IoC Container가 담당한다.
// DI 방식 — 느슨한 결합
@Injectable()
class OrderService {
constructor(private readonly userService: UserService) {} // 주입받음
}
IoC(Inversion of Control): 제어의 역전. 객체 생성과 생명주기 관리의 주체를 개발자에서 프레임워크(Container)로 넘기는 설계 원칙.
앱 부트스트랩
│
▼
NestJS IoC Container
│
├── @Injectable() 클래스 스캔
├── 의존성 그래프 생성
└── 인스턴스 생성 & 주입
│
▼
Module A Module B
┌──────────┐ ┌──────────┐
│Controller│ │Controller│
│ │ │ │ │ │
│ Service │ │ Service │
└──────────┘ └──────────┘
NestJS는 앱이 시작될 때 모든 @Injectable 클래스를 스캔하고, TypeScript의 메타데이터를 통해 생성자 파라미터 타입을 읽어 의존성 그래프를 구성한다. 이후 필요한 순서대로 인스턴스를 생성해 주입한다.
NestJS에서 권장하는 주입 방식은 Constructor Injection이다.
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.userRepository.find();
}
}
생성자 파라미터로 의존성을 선언하면, Container가 해당 타입의 인스턴스를 찾아 자동으로 넘겨준다. readonly를 사용하면 주입 이후 재할당을 방지할 수 있다.
| 스코프 | 동작 | 사용 시점 |
|---|---|---|
DEFAULT (Singleton) | 앱 전체에서 인스턴스 1개 공유 | 대부분의 Service |
REQUEST | HTTP 요청마다 새 인스턴스 생성 | 요청별 상태가 필요할 때 |
TRANSIENT | 주입될 때마다 새 인스턴스 생성 | 격리된 상태가 필요할 때 |
기본 스코프인 Singleton은 메모리 효율이 높고 초기화 비용이 한 번뿐이다. 다만 인스턴스가 공유되므로 내부에 가변 상태를 두면 안 된다.
결합도 감소: OrderService는 UserService가 어떻게 만들어지는지 모른다. 구현체가 바뀌어도 OrderService 코드는 그대로다.
테스트 용이: 테스트 시 실제 구현체 대신 Mock을 주입할 수 있다.
const module = await Test.createTestingModule({
providers: [
OrderService,
{ provide: UserService, useValue: mockUserService }, // Mock 주입
],
}).compile();
Container가 의존성 생성을 관리하기 때문에, 테스트 환경에서 원하는 구현체로 교체하는 것이 간단하다. 이것이 NestJS에서 단위 테스트가 자연스럽게 구조화되는 이유다.