1. What is a Module?
Module is the unit of code organization in NestJS. Every application has at least one module — root module (AppModule). NestJS encourages code organization accordingly feature modules, each module encapsulates a group of related functions.
@Module({
imports: [], // Modules khác mà module này cần
controllers: [], // Controllers thuộc module này
providers: [], // Services/Providers thuộc module này
exports: [], // Providers được chia sẻ ra ngoài
})
export class UsersModule {}
2. Feature Modules
// Cấu trúc thư mục theo feature
src/
├── app.module.ts // Root module
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ └── update-user.dto.ts
│ └── entities/
│ └── user.entity.ts
├── products/
│ ├── products.module.ts
│ ├── products.controller.ts
│ ├── products.service.ts
│ └── ...
└── orders/
├── orders.module.ts
└── ...
// users.module.ts
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // Cho phép module khác dùng UsersService
})
export class UsersModule {}
// orders.module.ts — cần dùng UsersService
@Module({
imports: [UsersModule], // Import để dùng exported providers
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {}
// app.module.ts — Root module
@Module({
imports: [UsersModule, ProductsModule, OrdersModule],
})
export class AppModule {}
3. Shared Modules
// Shared module — chứa các utilities dùng chung
@Module({
providers: [
HelperService,
SlugService,
PaginationService,
],
exports: [
HelperService,
SlugService,
PaginationService,
],
})
export class SharedModule {}
// Sử dụng trong nhiều modules
@Module({
imports: [SharedModule], // Mỗi module cần import riêng
// ...
})
export class UsersModule {}
@Module({
imports: [SharedModule],
// ...
})
export class ProductsModule {}
4. Global Modules
// Global module — không cần import trong từng module
@Global()
@Module({
providers: [
ConfigService,
LoggerService,
CacheService,
],
exports: [
ConfigService,
LoggerService,
CacheService,
],
})
export class CoreModule {}
// Chỉ cần import một lần trong AppModule
@Module({
imports: [CoreModule, UsersModule, ProductsModule],
})
export class AppModule {}
// Bây giờ mọi module đều có thể inject ConfigService
// mà KHÔNG cần import CoreModule
@Injectable()
export class UsersService {
constructor(private config: ConfigService) {} // ✅ Works!
}
⚠️ Warning: Do not abuse @Global(). Only used for truly global services such as Config, Logger, Cache.
5. Dynamic Modules
// Dynamic module — cấu hình tại thời điểm import
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
global: true,
providers: [
{ provide: 'DATABASE_OPTIONS', useValue: options },
{
provide: 'DATABASE_CONNECTION',
useFactory: async (opts: DatabaseOptions) => {
return await createConnection(opts);
},
inject: ['DATABASE_OPTIONS'],
},
DatabaseService,
],
exports: [DatabaseService, 'DATABASE_CONNECTION'],
};
}
// forFeature — đăng ký entities/repositories
static forFeature(entities: Type[]): DynamicModule {
const repositories = entities.map(entity => ({
provide: getRepositoryToken(entity),
useFactory: (connection: Connection) => connection.getRepository(entity),
inject: ['DATABASE_CONNECTION'],
}));
return {
module: DatabaseModule,
providers: repositories,
exports: repositories,
};
}
}
// Sử dụng
@Module({
imports: [
DatabaseModule.forRoot({ // Root config
host: 'localhost',
port: 5432,
database: 'myapp',
}),
],
})
export class AppModule {}
@Module({
imports: [
DatabaseModule.forFeature([User, Product]), // Feature entities
],
})
export class UsersModule {}
6. Re-exporting module
@Module({
imports: [CommonModule],
exports: [CommonModule], // Re-export cả module
})
export class CoreModule {}
// Ai import CoreModule sẽ tự động có access CommonModule
7. Handling Circular Dependency
// ❌ Circular: UsersModule ↔ OrdersModule
// UsersModule imports OrdersModule
// OrdersModule imports UsersModule
// ✅ Giải pháp: forwardRef()
@Module({
imports: [forwardRef(() => OrdersModule)],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Module({
imports: [forwardRef(() => UsersModule)],
providers: [OrdersService],
exports: [OrdersService],
})
export class OrdersModule {}
// Trong service
@Injectable()
export class UsersService {
constructor(
@Inject(forwardRef(() => OrdersService))
private ordersService: OrdersService,
) {}
}
Best Practice: Avoid circular dependency. If encountered, separate the common logic into a shared module or use Event-based communication.
8. Summary
- Feature Modules: Organize code according to business domain
- Shared Modules: Contains shared utilities, needs import
- Global Modules:
@Global(), automatically available everywhere - Dynamic Modules:
forRoot()/forFeature()pattern. pattern - Avoid circular dependencies, prefer event-based communication
Next post will connect Database with TypeORM and Prisma.