Trong loạt bài viết về NestJS, một trong những khái niệm được giới thiệu và thường gặp trong các buổi phỏng vấn là Module Động (Dynamic Module). Vậy Module Động là gì và tại sao nó lại quan trọng đối với lập trình viên NestJS trong phát triển Backend?
Dynamic Module trong NestJS là một tính năng mạnh mẽ cho phép bạn xây dựng các module có thể cấu hình linh hoạt. Tính năng này rất hữu ích khi bạn cần tái sử dụng một module với các cấu hình khác nhau hoặc khi có nhiều điều kiện cụ thể cần đáp ứng trong ứng dụng của bạn.
Bước 1: Tạo Module Động
Cấu trúc Module
typescript
// database.module.ts
import { Module, DynamicModule } from '@nestjs/common';
@Module({})
export class DatabaseModule {
static forRoot(options: { type: string; host: string }): DynamicModule {
const providers = [
{
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
if (options.type === 'mysql') {
return `Kết nối MySQL tại ${options.host}`;
} else if (options.type === 'mongodb') {
return `Kết nối MongoDB tại ${options.host}`;
}
throw new Error('Loại cơ sở dữ liệu không được hỗ trợ!');
},
},
];
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
Bước 2: Sử dụng Module Động trong ứng dụng
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database.module';
@Module({
imports: [
DatabaseModule.forRoot({
type: 'mysql',
host: 'localhost',
}),
],
})
export class AppModule {}
Bước 3: Inject và sử dụng kết nối
typescript
// app.service.ts
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject('DATABASE_CONNECTION') private readonly connection: string) {}
getConnectionInfo(): string {
return this.connection;
}
}
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('connection')
getConnection(): string {
return this.appService.getConnectionInfo();
}
}
Giải thích về Module Động
-
DatabaseModule
:- Đây là một module động.
- Phương thức
forRoot
trả về một Dynamic Module với khả năng cấu hình linh hoạt. - Dựa trên
options.type
, nó sẽ tạo một kết nối tương ứng cho MySQL hoặc MongoDB.
-
Sử dụng trong
AppModule
:- Gọi
DatabaseModule.forRoot(...)
để cung cấp cấu hình kết nối cơ sở dữ liệu.
- Gọi
-
Inject và sử dụng trong service/controller:
- Sử dụng
DATABASE_CONNECTION
để lấy thông tin kết nối cơ sở dữ liệu.
- Sử dụng
Chạy ứng dụng
Khi bạn chạy ứng dụng và truy cập endpoint /connection
, bạn sẽ nhận được kết quả như sau (giả sử type
là mysql
và host
là localhost
):
"Kết nối MySQL tại localhost"
Triển khai DatabaseModule
với forRoot()
và forFeature()
1. Cấu hình DatabaseModule
typescript
// database.module.ts
import { Module, DynamicModule } from '@nestjs/common';
@Module({})
export class DatabaseModule {
static forRoot(options: { type: string; host: string }): DynamicModule {
const providers = [
{
provide: 'DATABASE_CONNECTION',
useValue: `Kết nối ${options.type} tại ${options.host}`,
},
];
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
static forFeature(entities: string[]): DynamicModule {
const providers = entities.map((entity) => ({
provide: `${entity.toUpperCase()}_REPOSITORY`,
useValue: `${entity} repository`,
}));
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}
forRoot(options)
: Cung cấp thông tin kết nối chung cho cơ sở dữ liệu.forFeature(entities)
: Định nghĩa repository cụ thể cho các bảng trong cơ sở dữ liệu.
2. Sử dụng forRoot()
trong AppModule
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database.module';
import { UserModule } from './user.module';
@Module({
imports: [
DatabaseModule.forRoot({ type: 'mysql', host: 'localhost' }),
UserModule,
],
})
export class AppModule {}
3. Sử dụng forFeature()
trong module con
Tạo UserModule
typescript
// user.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database.module';
import { UserService } from './user.service';
@Module({
imports: [
DatabaseModule.forFeature(['user', 'profile']),
],
providers: [UserService],
})
export class UserModule {}
Dịch vụ sử dụng repository
typescript
// user.service.ts
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class UserService {
constructor(
@Inject('USER_REPOSITORY') private readonly userRepository: string,
@Inject('PROFILE_REPOSITORY') private readonly profileRepository: string,
) {}
getRepositories(): string[] {
return [this.userRepository, this.profileRepository];
}
}
// user.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get('repositories')
getRepositories(): string[] {
return this.userService.getRepositories();
}
}
Kết quả
Khi bạn truy cập vào endpoint /users/repositories
, bạn sẽ nhận được:
[ "user repository", "profile repository" ]
Chương trình sẽ sử dụng kết nối MySQL được cấu hình bởi forRoot()
và module UserModule
nhận thêm các repository thông qua forFeature()
.
Khi nào nên dùng forRoot()
và forFeature()
?
Sử dụng forRoot()
khi:
- Cần cấu hình chung cho toàn ứng dụng.
- Chỉ cần gọi một lần trong module gốc (ví dụ:
AppModule
).
Sử dụng forFeature()
khi:
- Cần mở rộng cấu hình cho các module con.
- Định nghĩa các entities hay repository cho các bảng dữ liệu.
Tóm tắt
forRoot()
= Cấu hình toàn ứng dụng.forFeature()
= Cấu hình cục bộ cho từng module con.- Kết hợp
forRoot()
vàforFeature()
sẽ tạo ra hệ thống module linh hoạt và mô-đun, đặc biệt trong các dự án lớn.
Tài liệu tham khảo
- Tài liệu chính thức về Module Động trong NestJS
- Ghim trên GitHub với triển khai ví dụ
- Tham khảo dự án trên GitHub
Hãy đọc bài viết gốc tại: Trần Nhật Sang để tìm hiểu sâu hơn về Dynamic Module trong NestJS.
source: viblo