Building Modular NestJS Applications
- Published on
- Published on
- /4 mins read/---
Core Module Structure
First, let's set up the core module structure:
// core/core.module.ts
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { LoggerService } from './services/logger.service';
import { DatabaseService } from './services/database.service';
import { CacheService } from './services/cache.service';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.local', '.env']
})
],
providers: [
LoggerService,
DatabaseService,
CacheService
],
exports: [
LoggerService,
DatabaseService,
CacheService
]
})
export class CoreModule {}
// core/services/logger.service.ts
import { Injectable } from '@nestjs/common';
import { createLogger, format, transports } from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';
@Injectable()
export class LoggerService {
private logger;
constructor() {
this.logger = createLogger({
format: format.combine(
format.timestamp(),
format.json()
),
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.simple()
)
}),
new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d'
})
]
});
}
log(level: string, message: string, meta?: any) {
this.logger.log(level, message, meta);
}
}
Feature Module Example
Here's an example of a feature module:
// features/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UserRepository } from './repositories/user.repository';
import { UserEventsSubscriber } from './subscribers/user-events.subscriber';
@Module({
imports: [
TypeOrmModule.forFeature([User])
],
controllers: [UsersController],
providers: [
UsersService,
UserRepository,
UserEventsSubscriber
],
exports: [UsersService]
})
export class UsersModule {}
// features/users/entities/user.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ unique: true })
email: string;
@Column()
passwordHash: string;
@Column({ nullable: true })
name: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// features/users/repositories/user.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';
@Injectable()
export class UserRepository {
constructor(
@InjectRepository(User)
private repository: Repository<User>
) {}
async findById(id: string): Promise<User> {
return this.repository.findOne({ where: { id } });
}
async findByEmail(email: string): Promise<User> {
return this.repository.findOne({ where: { email } });
}
async create(data: Partial<User>): Promise<User> {
const user = this.repository.create(data);
return this.repository.save(user);
}
}
Shared Module Example
Create a shared module for common functionality:
// shared/shared.module.ts
import { Module } from '@nestjs/common';
import { ValidationService } from './services/validation.service';
import { UtilsService } from './services/utils.service';
import { CommonPipe } from './pipes/common.pipe';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
@Module({
providers: [
ValidationService,
UtilsService,
CommonPipe,
TimeoutInterceptor
],
exports: [
ValidationService,
UtilsService,
CommonPipe,
TimeoutInterceptor
]
})
export class SharedModule {}
// shared/services/validation.service.ts
import { Injectable } from '@nestjs/common';
import { validate } from 'class-validator';
@Injectable()
export class ValidationService {
async validateObject<T extends object>(obj: T): Promise<string[]> {
const errors = await validate(obj);
return errors.map(error => Object.values(error.constraints)).flat();
}
}
// shared/interceptors/timeout.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
RequestTimeoutException
} from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError(err => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}
Application Module Configuration
Configure the main application module:
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
import { UsersModule } from './features/users/users.module';
import { AuthModule } from './features/auth/auth.module';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
username: config.get('DB_USERNAME'),
password: config.get('DB_PASSWORD'),
database: config.get('DB_DATABASE'),
entities: ['dist/**/*.entity{.ts,.js}'],
synchronize: config.get('NODE_ENV') !== 'production',
logging: config.get('DB_LOGGING', false)
})
}),
CoreModule,
SharedModule,
UsersModule,
AuthModule
]
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
import { TimeoutInterceptor } from './shared/interceptors/timeout.interceptor';
import { LoggerService } from './core/services/logger.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: false
});
// Global configurations
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
transform: true
}));
app.useGlobalInterceptors(new TimeoutInterceptor());
// Custom logger
const logger = app.get(LoggerService);
app.useLogger(logger);
await app.listen(3000);
}
bootstrap();
Module Organization Best Practices
Core Module
- Global providers
- Configuration
- Database connections
- Logging services
Shared Module
- Common utilities
- Shared pipes
- Shared interceptors
- Validation services
Feature Modules
- Domain-specific logic
- Feature controllers
- Feature services
- Feature entities
Module Dependencies
AppModule ├── CoreModule ├── SharedModule └── FeatureModules ├── UsersModule ├── AuthModule └── OtherFeatureModules
Notes
- Keep modules focused and cohesive
- Use dependency injection
- Implement proper error handling
- Follow SOLID principles
- Use TypeORM decorators correctly
- Implement proper validation
- Handle configuration properly
- Use proper logging
- Implement proper security measures