Table of Contents
- What Are Modules in NestJS?
- Why Modules Matter: Core to Nest’s Architecture
- Exploring the
@Module()
Decorator - Imports, Exports, Providers, and Controllers
- Shared Modules and Feature Modules
- Best Practices for Module Organization
- Lazy Loading and Dynamic Modules (Advanced Preview)
- Summary and What’s Next
1. What Are Modules in NestJS?
In NestJS, everything revolves around modules. Modules are TypeScript classes decorated with @Module()
that organize your application into cohesive blocks of functionality.
Each NestJS application has at least one module—the root module, commonly called AppModule
.
A module serves as a container for related:
- Controllers
- Providers (Services, etc.)
- Other imported modules
Think of modules as the glue that brings your features together.
2. Why Modules Matter: Core to Nest’s Architecture
NestJS is heavily inspired by Angular’s modular architecture and encourages modular design:
- Separation of concerns: Each module should have a clear responsibility.
- Scalability: Easier to break the application into smaller domains or microservices.
- Reusability: Share logic or utilities via shared modules.
- Maintainability: Easier to test and refactor.
3. Exploring the @Module()
Decorator
Here’s the shape of a basic module:
tsCopyEditimport { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
exports: [],
})
export class AppModule {}
The @Module()
decorator accepts:
- imports – External modules your module needs
- controllers – All route handlers for HTTP requests
- providers – Services and other providers for DI
- exports – What this module exposes to others
4. Imports, Exports, Providers, and Controllers
imports
Use this to bring in functionality from other modules.
tsCopyEdit@Module({
imports: [UsersModule],
})
export class OrdersModule {}
providers
Define your business logic (like services, use-cases).
tsCopyEditproviders: [UserService, JwtStrategy]
controllers
Handle incoming requests and pass control to services.
tsCopyEditcontrollers: [UserController]
exports
Let other modules reuse your services/providers.
tsCopyEditexports: [UserService]
Without exporting, your module’s services stay private.
5. Shared Modules and Feature Modules
Feature Module
Encapsulates a domain or use case (e.g., UserModule
, AuthModule
).
tsCopyEdit@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
Shared Module
A module with common utilities/services used across your app.
tsCopyEdit@Module({
providers: [LoggerService],
exports: [LoggerService],
})
export class SharedModule {}
Important: Avoid re-providing a service in multiple modules if it’s already exported. Use @Global()
for globally available shared modules.
6. Best Practices for Module Organization
- 1 module per feature: e.g.,
UserModule
,OrderModule
,AuthModule
- Keep modules focused: Don’t overstuff a module with unrelated logic
- Use barrel exports: Make it easier to import shared functionality
- Avoid circular dependencies: Modularize carefully
- Use DTOs and interfaces: Maintain strict boundaries
7. Lazy Loading and Dynamic Modules (Advanced Preview)
You can define Dynamic Modules using register()
or registerAsync()
for things like database connections or configurable services:
tsCopyEdit@Module({})
export class DatabaseModule {
static register(options: DBOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [
{
provide: DB_CONFIG,
useValue: options,
},
],
exports: [DB_CONFIG],
};
}
}
This allows flexibility when importing modules with custom behavior.
8. Summary and What’s Next
In this module, you’ve learned how NestJS uses modules to enforce structure and scalability. Mastering the modular approach is essential before diving deeper into controllers, services, and dependency injection.