NestJS Modules Guide
NestJS module system: feature modules, shared modules, dynamic modules, circular dependencies, and modular application architecture.
1. Feature Module
// src/articles/articles.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ArticlesController } from './articles.controller';
import { ArticlesService } from './articles.service';
import { Article } from './entities/article.entity';
import { UsersModule } from '../users/users.module';
@Module({
imports: [
TypeOrmModule.forFeature([Article]), // register entity in this scope
UsersModule, // use exports from UsersModule
],
controllers: [ArticlesController],
providers: [ArticlesService],
exports: [ArticlesService], // expose to other modules
})
export class ArticlesModule {}
// app.module.ts
@Module({
imports: [
TypeOrmModule.forRoot({ ... }),
ArticlesModule,
UsersModule,
AuthModule,
],
})
export class AppModule {}
2. Shared Module
// src/common/common.module.ts
@Global() // available everywhere without importing
@Module({
providers: [
LoggerService,
{ provide: 'APP_CONFIG', useValue: { debug: true } },
],
exports: [LoggerService, 'APP_CONFIG'],
})
export class CommonModule {}
// Custom provider with factory
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: async (configService: ConfigService) => {
const client = createClient({ url: configService.get('REDIS_URL') });
await client.connect();
return client;
},
inject: [ConfigService],
},
],
exports: ['REDIS_CLIENT'],
})
export class RedisModule {}
3. Dynamic Modules
// src/mailer/mailer.module.ts
export interface MailerOptions {
host: string;
port: number;
auth: { user: string; pass: string };
from: string;
}
@Module({})
export class MailerModule {
static register(options: MailerOptions): DynamicModule {
return {
module: MailerModule,
providers: [
{ provide: 'MAILER_OPTIONS', useValue: options },
MailerService,
],
exports: [MailerService],
};
}
// Async version — load from ConfigService
static registerAsync(asyncOptions: {
useFactory: (...args: any[]) => MailerOptions | Promise<MailerOptions>;
inject?: any[];
}): DynamicModule {
return {
module: MailerModule,
providers: [
{
provide: 'MAILER_OPTIONS',
useFactory: asyncOptions.useFactory,
inject: asyncOptions.inject ?? [],
},
MailerService,
],
exports: [MailerService],
};
}
}
// Usage
MailerModule.registerAsync({
useFactory: (cfg: ConfigService) => ({
host: cfg.get('SMTP_HOST'),
port: cfg.getOrThrow<number>('SMTP_PORT'),
auth: { user: cfg.get('SMTP_USER'), pass: cfg.get('SMTP_PASS') },
from: cfg.get('SMTP_FROM'),
}),
inject: [ConfigService],
})
4. Provider Types
@Module({
providers: [
// Class provider (default)
ArticlesService,
// Explicit class provider
{ provide: ArticlesService, useClass: ArticlesService },
// Value provider
{ provide: 'API_VERSION', useValue: 'v1' },
// Factory provider
{
provide: 'RANDOM_ID',
useFactory: () => Math.random().toString(36).slice(2),
},
// Factory with dependencies
{
provide: 'DB_POOL',
useFactory: (cfg: ConfigService) => new Pool({ connectionString: cfg.get('DB_URL') }),
inject: [ConfigService],
},
// Existing alias
{ provide: 'ALIAS', useExisting: ArticlesService },
],
})
5. Module Organization Pattern
src/
├── app.module.ts
├── main.ts
├── common/
│ ├── common.module.ts
│ ├── logger/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/
│ └── config.module.ts
├── database/
│ └── database.module.ts
├── auth/
│ ├── auth.module.ts
│ ├── auth.controller.ts
│ ├── auth.service.ts
│ └── strategies/
└── articles/
├── articles.module.ts
├── articles.controller.ts
├── articles.service.ts
├── dto/
├── entities/
└── interfaces/
6. Module Metadata Properties
| Property | Purpose |
|---|---|
| imports | Other modules to import (access their exports) |
| controllers | Route controllers in this module |
| providers | Services, guards, pipes, interceptors |
| exports | Subset of providers available to other modules |