| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- next.js
- 네트워크
- 피드백
- WIL
- til
- 주간회고
- Git
- MongoDB
- array
- 생각일기
- mysql
- nest.js
- Grafana
- CS
- 알고리즘
- typescript
- 리눅스
- 코테
- 생각로그
- mongoose
- javascript
- 트러블슈팅
- js
- 자바스크립트
- 기록
- 생각정리
- mongo
- 회고
- Java
- react
- Today
- Total
코딩일상
[nest.js] nest.js 뿌수기 공식 docs 모조리 파헤치기[Modules] 본문

1. Module 기본 개념
Module은 @Module() 데코레이터로 정의된 클래스입니다.
NestJS가 애플리케이션 구조를 효율적으로 조직화하고 관리하는 데 사용하는 메타데이터를 제공
모든 NestJS 애플리케이션은 최소한 하나의 root module을 가지며, 이것이 Nest가 application graph를 구축하는 시작점이 됩니다. 이 그래프는 Nest가 모듈과 프로바이더 간의 관계와 의존성을 해결하는 내부 구조입니다.
@Module() 데코레이터 속성
| providers | Nest injector에 의해 인스턴스화되고 최소한 이 모듈 내에서 공유될 수 있는 프로바이더들 |
| controllers | 이 모듈에서 정의되고 인스턴스화되어야 하는 컨트롤러 세트 |
| imports | 이 모듈에서 필요한 프로바이더를 export하는 다른 모듈들의 리스트 |
| exports | 이 모듈에서 제공하고, 이 모듈을 import하는 다른 모듈에서 사용 가능해야 하는 프로바이더들의 부분집합 |
캡슐화: 모듈은 기본적으로 프로바이더를 캡슐화합니다. 현재 모듈의 일부이거나 다른 imported 모듈에서 명시적으로 export된 프로바이더만 주입할 수 있습니다.
2. Feature Modules
// cats/cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
Feature Module은 특정 기능과 관련된 코드를 조직화하는 모듈입니다. 이것은 명확한 경계를 유지하고 더 나은 조직화를 돕습니다. 애플리케이션이나 팀이 성장할수록 중요하며, SOLID 원칙과 일치합니다.
CLI로 모듈 생성
$ nest g module cats
Root Module에 Import
// app.module.ts
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
디렉토리 구조
src/
├── cats/
│ ├── dto/
│ │ └── create-cat.dto.ts
│ ├── interfaces/
│ │ └── cat.interface.ts
│ ├── cats.controller.ts
│ ├── cats.module.ts
│ └── cats.service.ts
├── app.module.ts
└── main.ts
3. Shared Modules (공유 모듈)
NestJS에서 모듈은 기본적으로 싱글톤입니다. 따라서 여러 모듈 간에 동일한 프로바이더 인스턴스를 쉽게 공유할 수 있습니다.
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService] // 다른 모듈에서 사용 가능하도록 export
})
export class CatsModule {}
핵심 개념:
- 모든 모듈은 자동으로 shared module입니다
- exports 배열에 추가하면 다른 모듈에서 사용 가능
- 같은 인스턴스를 모든 모듈이 공유하여 메모리 효율적
왜 모듈에 캡슐화하는가?
- 각 모듈에서 직접 CatsService를 등록하면 각각 별도의 인스턴스가 생성됨
- 메모리 사용량 증가 및 상태 불일치 가능성
- 모듈에 캡슐화하고 export하면 동일한 인스턴스 재사용
4. Module Re-exporting
모듈은 자신의 내부 프로바이더를 export할 뿐만 아니라, import한 모듈도 re-export할 수 있습니다.
@Module({
imports: [CommonModule],
exports: [CommonModule], // CommonModule을 re-export
})
export class CoreModule {}
이렇게 하면 CoreModule을 import하는 다른 모듈에서 CommonModule도 사용할 수 있습니다.
5. Dependency Injection in Modules
모듈 클래스 자체도 프로바이더를 주입받을 수 있습니다 (예: 설정 목적).
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private catsService: CatsService) {}
}
주의: 모듈 클래스 자체는 circular dependency 때문에 프로바이더로 주입될 수 없습니다.
6. Global Modules
동일한 모듈 세트를 어디서나 import해야 한다면 번거로울 수 있습니다. @Global() 데코레이터를 사용하면 모듈을 전역 범위로 만들 수 있습니다.
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
특징:
- Global 모듈은 한 번만 등록되어야 함 (일반적으로 root 또는 core 모듈에서)
- CatsService를 사용하려는 모듈이 CatsModule을 imports 배열에 추가할 필요 없음
- 모든 것을 global로 만드는 것은 권장되지 않음 - imports 배열을 사용하는 것이 더 명확하고 유지보수 가능
7. Dynamic Modules(완벽 이해 부족 추가 공부 정리 예정)
Dynamic Modules는 런타임에 구성할 수 있는 모듈을 만들 수 있게 합니다. 특정 옵션이나 설정에 따라 프로바이더를 생성할 때 유용합니다.
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
exports: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
주요 특징
- 동적 프로바이더 생성: entities와 options 객체에 따라 프로바이더 컬렉션(예: repositories) 생성
- 메타데이터 확장: 동적 모듈이 반환하는 속성은 @Module() 데코레이터에 정의된 기본 메타데이터를 확장(덮어쓰지 않음)
- 동기/비동기 지원: forRoot() 메서드는 동기적으로 또는 비동기적으로(Promise를 통해) 반환 가능
Global Dynamic Module
{
global: true, // 전역 범위로 설정
module: DatabaseModule,
providers: providers,
exports: providers,
}
사용 예시
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}
Re-exporting Dynamic Module
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule], // forRoot() 호출 생략 가능
})
export class AppModule {}
ConfigurableModuleBuilder
더 고급 사용을 위해 ConfigurableModuleBuilder를 사용하여 고도로 커스터마이징 가능한 동적 모듈을 구축할 수 있습니다.
핵심 정리
- 모듈은 NestJS의 기본 조직 단위 - 관련된 컴포넌트들을 그룹화
- 싱글톤 패턴 - 모듈은 기본적으로 싱글톤이며 프로바이더 인스턴스를 공유
- 캡슐화 - 명시적으로 export하지 않으면 프로바이더는 모듈 외부에서 사용 불가
- 재사용성 - export를 통해 다른 모듈에서 프로바이더 재사용
- 전역 범위 - @Global() 데코레이터로 어디서나 사용 가능하게 만들 수 있지만, 남용 금지
- 동적 구성 - Dynamic Modules로 런타임에 모듈 구성 가능
Module을 사용하는 것 vs 안 하는 것 - 실생활 비유로 이해하기
1. 🏢 회사 조직 비유
Module 없이 개발 (모든 직원이 한 사무실)
// 😱 모든 것이 한 곳에!
class AppModule {
providers: [
// 유저 관련 (20개)
UserService, UserRepository, UserValidator, EmailService, PasswordHasher, ...
// 상품 관련 (30개)
ProductService, ProductRepository, InventoryService, PriceCalculator, ...
// 주문 관련 (25개)
OrderService, OrderRepository, PaymentService, ShippingService, ...
// 인증 관련 (15개)
AuthService, JwtService, TokenValidator, RefreshTokenService, ...
// 총 90개의 Service들이 한 파일에!
]
}
문제점:
- 📦 모든 직원(Service)이 한 사무실에 모여있음
- 🤯 누가 누구랑 일하는지 알 수 없음
- 🐌 회사 규모가 커지면 혼란만 가중
- 🔍 특정 팀 찾기가 어려움
Module 사용 (부서별로 조직화)
// 👥 유저 팀 (HR팀 같은 느낌)
@Module({
providers: [UserService, UserRepository, EmailService],
exports: [UserService] // 다른 부서에서 쓸 수 있게 공개
})
class UserModule {}
// 📦 상품 팀 (영업팀 같은 느낌)
@Module({
providers: [ProductService, ProductRepository, InventoryService],
exports: [ProductService]
})
class ProductModule {}
// 💳 주문 팀 (재무팀 같은 느낌)
@Module({
imports: [UserModule, ProductModule], // 다른 팀과 협업
providers: [OrderService, PaymentService],
exports: [OrderService]
})
class OrderModule {}
// 🏢 본사 (전체 통합)
@Module({
imports: [UserModule, ProductModule, OrderModule]
})
class AppModule {}
장점:
- ✅ 각 팀이 자기 일에 집중
- ✅ 팀 간 협업 관계가 명확
- ✅ 새 직원이 와도 어느 팀인지 바로 알 수 있음
- ✅ 팀 단위로 테스트 가능
