Module Concept
NestJS modules are the basic organizational units of an application. Each module is a class decorated with @Module(). Modules organize related components (controllers, providers, etc.) together to form cohesive functional units.
Basic Module Structure
typescriptimport { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ imports: [], // Import other modules controllers: [UsersController], // Declare controllers providers: [UsersService], // Declare providers exports: [UsersService], // Export providers for other modules }) export class UsersModule {}
Module Decorator Properties
- imports: Import other modules to make their providers available
- controllers: Declare controllers belonging to this module
- providers: Declare providers belonging to this module
- exports: Export providers to make them available to other modules
- Difference between providers and exports: providers are only available within the current module, exports can be used by other modules
Module Types
- Root Module: The entry module of the application
- Feature Module: Modules that encapsulate specific functionality
- Shared Module: Modules that export providers for use by multiple modules
- Global Module: Modules decorated with
@Global()that are automatically imported into all modules
Dependency Injection
Dependency injection is a core design pattern of NestJS. It implements Inversion of Control (IoC), making code more loosely coupled, testable, and maintainable.
How Dependency Injection Works
- Provider Registration: Register providers in the module
- Dependency Declaration: Declare dependencies in the constructor
- Automatic Resolution: NestJS automatically resolves and injects dependencies
Basic Example
typescript@Injectable() export class UsersService { constructor(private readonly userRepository: UserRepository) {} async findAll() { return this.userRepository.findAll(); } } @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() findAll() { return this.usersService.findAll(); } }
Advantages of Dependency Injection
- Loose Coupling: Components interact through interfaces rather than specific implementations
- Testability: Easy to replace dependencies in tests
- Maintainability: Clear dependency relationships make modifications easier
- Reusability: Providers can be used in multiple places
Scopes
NestJS provides three dependency injection scopes:
1. Default Scope (Singleton)
typescript@Injectable() export class UsersService {}
- Only one instance exists throughout the entire application
- Suitable for stateless services
2. Request Scope
typescript@Injectable({ scope: Scope.REQUEST }) export class UsersService {}
- Creates a new instance for each request
- Suitable for services that need request-specific state
3. Transient Scope
typescript@Injectable({ scope: Scope.TRANSIENT }) export class UsersService {}
- Creates a new instance for each injection
- Suitable for scenarios requiring independent instances
Circular Dependencies
Circular dependencies occur when two or more modules depend on each other. NestJS provides several solutions:
1. Using forwardRef()
typescript@Module({ imports: [forwardRef(() => BModule)], }) export class AModule {} @Module({ imports: [forwardRef(() => AModule)], }) export class BModule {}
2. Refactor Code Structure
- Extract shared functionality into separate modules
- Use event-driven architecture instead of direct dependencies
Best Practices
- Modular Design: Divide modules by functional domains
- Single Responsibility: Each module should only be responsible for one functional domain
- Minimize Dependencies: Avoid unnecessary dependencies
- Use Interfaces: Define dependency contracts through interfaces
- Avoid Circular Dependencies: Avoid circular dependencies between modules during design
- Use Scopes Appropriately: Choose the appropriate scope based on requirements
- Export Necessary Providers: Only export providers that need to be used by other modules
Summary
NestJS's module and dependency injection system is the core of its architecture, providing:
- Clear code organization structure
- Loosely coupled component design
- High testability
- Good maintainability
Mastering modules and dependency injection is the foundation for building high-quality applications with NestJS, enabling developers to build scalable, maintainable enterprise applications.