Trong bài viết này chúng ta sẽ đi sâu về dependency Inject và custom provider trong NestJs, mục tiêu của bài viết này là:

  • Hiểu rõ cách hoạt động của Dependency Injection (DI) trong NestJS.

  • Biết cách tạo Custom Provider với useClass, useValue, useFactory.

  • Biết xử lý circular dependency với forwardRef.

1.  Dependency Injection là gì?

Dependency Injection (DI) là một thiết kế giúp tách biệt việc tạo ra phụ thuộc (dependency) ra khỏi class sử dụng chúng. Nói có vẻ khó hiểu, ví dụ thay vì tự tạo instance của một class khác ở trong 1 service hay 1 class nào đó, chúng ta sẽ "tiêm" vào thông qua constructor, setter hoặc trong chính hàm đó, điều này giúp cho ứng dụng:

  • Dễ test

  • Dễ mở rộng

  • Dễ mock

Trong NestJS, hệ thống DI hoạt động dựa trên providercontainer.

2. Provider là gì?

@Injectable()
export class UserService {
  constructor(private readonly db: DatabaseService) {}
}

Ở đây, DatabaseServicedependency được inject vào từ DI container. Nest sẽ tự tìm DatabaseService và inject vào UserService.

Mặc định, Nest dùng useClass để map 1 token với 1 class.

3. Custom Provider trong NestJS

Nest cho phép tùy chỉnh cách khởi tạo provider bằng 3 cách chính:

3.1. useClass – class mapping đơn giản

const myProvider = {
  provide: 'DatabaseService',
  useClass: MysqlDatabaseService,
};

Khi inject 'DatabaseService', Nest sẽ khởi tạo MysqlDatabaseService.

3.2. useValue – inject 1 giá trị có sẵn

const myProvider = {
  provide: 'API_KEY',
  useValue: 'my-secret-key-123',
};

Inject vào

constructor(@Inject('API_KEY') private readonly apiKey: string) {}

3.3. useFactory – custom logic khi khởi tạo

const myProvider = {
  provide: 'DatabaseConnection',
  useFactory: async () => {
    const conn = await createConnection(); // custom logic
    return conn;
  },
};

Inject

constructor(@Inject('DatabaseConnection') private readonly conn: any) {}

4. Xử lý Circular Dependency bằng forwardRef

Khi Service A cần Service B, và ngược lại Service B cũng cần A, bạn sẽ gặp lỗi:

Error: A circular dependency has been detected...

Cách xử lý là dùng forwardRef():

Ví dụ

// auth.service.ts
@Injectable()
export class AuthService {
  constructor(
    @Inject(forwardRef(() => UserService))
    private readonly userService: UserService,
  ) {}
}
// user.service.ts
@Injectable()
export class UserService {
  constructor(
    @Inject(forwardRef(() => AuthService))
    private readonly authService: AuthService,
  ) {}
}

Và trong module

@Module({
  providers: [UserService, AuthService],
  imports: [forwardRef(() => AuthModule)],
  exports: [UserService],
})
export class UserModule {}

5. Khi nào dùng cái gì?

Loại Provider

Khi nào nên dùng

useClass

Khi bạn có 1 class triển khai cụ thể

useValue

Khi bạn cần inject 1 giá trị tĩnh (ví dụ API_KEY)

useFactory

Khi bạn cần tạo object có logic phức tạp

forwardRef

Khi gặp circular dependency giữa services

5. Kết luận

Hiểu rõ Dependency Injection và các loại Custom Provider là nền tảng quan trọng để phát triển ứng dụng NestJS theo kiến trúc linh hoạt, mở rộng và dễ test.

Dong Nguyen