[NestJS] Hiểu Sâu Về Dynamic Modules: Phân biệt register, forRoot và forFeature
Bạn đã bao giờ thắc mắc tại sao khi sử dụng TypeORM trong NestJS, chúng ta lại phải gọi TypeOrmModule.forRoot(...) ở AppModule, sau đó lại gọi TypeOrmModule.forFeature([User]) ở UserModule chưa? Vì sao không import thẳng là xong như các module thông thường?
Đó chính là lúc khái niệm Dynamic Module (Module Động) xuất hiện. Hôm nay, chúng ta sẽ cùng bóc tách kỹ thuật này một cách đơn giản nhất nhé!
1. Ẩn dụ: Mua Macbook vs Build PC Custom 💻
Để hiểu sự khác biệt giữa Static Module (Module tĩnh - cách import thông thường) và Dynamic Module, hãy tưởng tượng bạn đi mua máy tính:
- Static Module (Mua Macbook): Bạn ra store, mua một chiếc Macbook đã đóng gói sẵn cấu hình. Cứ mở hộp là dùng, không thể hoặc rất khó thay đổi phần cứng bên trong (RAM, Ổ cứng hàn chết). Trong code, đây là cách bạn
imports: [UserModule]. Mọi config bên trongUserModulelà cố định. - Dynamic Module (Build PC Custom): Bạn ra tiệm linh kiện, chọn một vỏ case (đại diện cho Module). Sau đó bạn đưa cho nhân viên danh sách linh kiện yêu cầu: "Lắp cho anh con card RTX 4090, 64GB RAM". Nhân viên sẽ dựa vào yêu cầu (configuration) của bạn để ráp thành một cỗ máy hoàn chỉnh mang về. Trong code, đây là lúc bạn gọi
ConfigModule.register({ folder: './config' }).
💡 Tóm lại: Dynamic Module cho phép chúng ta thay đổi hành vi, cấu hình của một module NGAY TẠI THỜI ĐIỂM IMPORT nó, thay vì code cứng từ trước.
2. Deep Dive: 3 Cấp độ Customization 🔬
Trong thế giới của NestJS, quy chuẩn thường sử dụng 3 static method để tạo Dynamic Module. Chúng không phải là từ khóa bắt buộc của framework, nhưng là Convention (Quy ước) mà toàn cộng đồng tuân theo.
2.1. register() - Cấu hình dùng riêng
Sử dụng khi bạn muốn cung cấp m ột cấu hình cụ thể cho module đó, và cấu hình này chỉ phục vụ cho module đang gọi (caller module).
Ví dụ: Bạn có một HttpModule và muốn gọi API tới các service khác nhau với timeout khác nhau.
// filename: src/orders/orders.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [
// Mỗi lần gọi register sẽ tạo ra một instance HttpService riêng biệt
HttpModule.register({
timeout: 5000,
baseURL: 'https://payment-gateway.example.com',
}),
],
})
export class OrdersModule {}
2.2. forRoot() - Cấu hình Global (Khởi tạo nền tảng)
Được sử dụng để cấu hình một lần duy nhất cho toàn bộ project (thường gọi ở AppModule). Mục đích là khởi tạo các kết nối nặng, các biến môi trường chung (Database Connection, Redis, Config Variables).
// filename: src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
// Chỉ gọi forRoot 1 lần ở gốc ứng dụng để tạo connection pool
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'admin',
password: 'password',
database: 'my_db',
autoLoadEntities: true,
synchronize: true,
}),
],
})
export class AppModule {}