# NestJS Enterprise Boilerplate This is a production-ready NestJS boilerplate designed for building scalable enterprise applications with MongoDB/Mongoose integration, JWT authentication, real-time WebSocket communication, message queuing with RabbitMQ, and Redis caching. The project follows best practices with a modular architecture, comprehensive type safety using TypeScript, and extensive infrastructure support including Docker, Swagger API documentation, and pre-configured development tooling. The boilerplate provides a complete foundation for REST APIs with features like role-based access control (RBAC), email services, file uploads (local/S3), rate limiting, request validation, pagination utilities, internationalization (i18next), logging with Winston, and health checks. It includes pre-built schemas for user management and todo/task tracking with advanced features like collaboration, time tracking, and hierarchical task structures. The architecture uses a consolidated API module pattern to avoid circular dependencies, global modules for cross-cutting concerns, and path aliases for clean imports. ## Health Check API Check application and database health status. ```bash curl http://localhost:3000/api/v1/health # Response { "status": "ok", "info": { "database": { "status": "up" } }, "error": {}, "details": { "database": { "status": "up" } } } ``` ## JWT Service Generate and verify JWT tokens for authentication with configurable expiration times. ```typescript import { JwtService } from '@lib/jwt'; import { Injectable } from '@nestjs/common'; @Injectable() export class AuthService { constructor(private readonly jwtService: JwtService) {} async login(userId: string, roles: string[]) { // Sign access token with default expiration (15m) const accessToken = this.jwtService.sign({ id: userId, roles: roles }); // Sign refresh token with custom expiration (7d) const refreshToken = this.jwtService.sign( { id: userId }, { expiresIn: this.jwtService.getConfig().JWT_REFRESH_EXPIRATION } ); return { accessToken, refreshToken }; } async verifyToken(token: string) { try { const payload = await this.jwtService.verifyAsync<{ id: string; roles: string[] }>(token); return payload; } catch (error) { throw new Error('Invalid token'); } } decodeToken(token: string) { return this.jwtService.decode(token); } } ``` ## Authentication Guard Protect routes with JWT authentication and support public endpoints. ```typescript import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'; import { Authenticate, Public, GetUser, IXAuthUser } from '@common'; @Controller('users') @Authenticate() // Apply to all routes in controller export class UsersController { // Protected route - requires JWT token @Get('profile') getProfile(@GetUser() user: IXAuthUser) { return { id: user.id, roles: user.roles, message: 'Protected profile data' }; } // Public route - accessible without token @Public() @Get('public-info') getPublicInfo() { return { message: 'This is public information' }; } // Public route that optionally extracts user if token provided @Public() @Get('optional-auth') getOptionalAuth(@GetUser() user: IXAuthUser) { if (user?.id) { return { message: 'Authenticated user', userId: user.id }; } return { message: 'Anonymous user' }; } } // Usage with authorization header // curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." http://localhost:3000/api/v1/users/profile ``` ## Redis Client Cache data and use pub/sub for real-time event distribution. ```typescript import { Injectable } from '@nestjs/common'; import { InjectIORedis, InjectIORedisPubSub } from '@lib/ioredis'; import Redis from 'ioredis'; @Injectable() export class CacheService { constructor( @InjectIORedis() private readonly redis: Redis, @InjectIORedisPubSub() private readonly pubsub: Redis ) { // Subscribe to expired keys notifications this.pubsub.on('message', (channel, message) => { if (channel === '__keyevent@0__:expired') { console.log('Key expired:', message); this.handleExpiredKey(message); } }); } async cacheUser(userId: string, userData: any, ttl: number = 3600) { await this.redis.setex(`user:${userId}`, ttl, JSON.stringify(userData)); } async getUser(userId: string) { const cached = await this.redis.get(`user:${userId}`); return cached ? JSON.parse(cached) : null; } async invalidateUser(userId: string) { await this.redis.del(`user:${userId}`); } async setSessionWithExpiry(sessionId: string, data: any, expirySeconds: number) { await this.redis.setex(sessionId, expirySeconds, JSON.stringify(data)); } private handleExpiredKey(key: string) { console.log(`Processing expired key: ${key}`); // Handle expired session, send notification, etc. } } ``` ## RabbitMQ Service Send and consume messages from queues and exchanges with durable configurations. ```typescript import { Injectable, OnModuleInit } from '@nestjs/common'; import { RabbitMQService } from '@lib/rabbitmq'; @Injectable() export class NotificationService implements OnModuleInit { constructor(private readonly rabbitmq: RabbitMQService) {} async onModuleInit() { // Start consuming email queue await this.rabbitmq.consumeQueue('email-queue', async (message) => { const emailData = JSON.parse(message); await this.sendEmail(emailData); }, { prefetch: 5 }); // Subscribe to user events exchange await this.rabbitmq.subscribeToExchange( 'user-events', 'user.registered', async (message) => { const userData = JSON.parse(message); await this.sendWelcomeEmail(userData); }, 'topic', { prefetch: 1 } ); } async queueEmail(to: string, subject: string, body: string) { await this.rabbitmq.sendToQueue('email-queue', { to, subject, body, timestamp: new Date().toISOString() }); } async publishUserEvent(eventType: string, userId: string, data: any) { await this.rabbitmq.publishToExchange( 'user-events', `user.${eventType}`, { userId, data, timestamp: Date.now() }, 'topic' ); } private async sendEmail(emailData: any) { console.log('Sending email:', emailData); // Email sending logic } private async sendWelcomeEmail(userData: any) { console.log('Sending welcome email to:', userData.email); } } ``` ## WebSocket Gateway Real-time bidirectional communication with room support and broadcasting. ```typescript import { Injectable } from '@nestjs/common'; import { EventGateway } from '@events'; @Injectable() export class RealtimeService { constructor(private readonly eventGateway: EventGateway) {} // Broadcast to all connected clients notifyAllUsers(message: string) { this.eventGateway.emit('notification', { message, timestamp: Date.now() }); } // Broadcast to specific room (user's personal room) notifyUser(userId: string, notification: any) { this.eventGateway.broadcast(`user:${userId}`, 'personal-notification', { ...notification, timestamp: Date.now() }); } // Broadcast to project room updateProjectStatus(projectId: string, status: string) { this.eventGateway.broadcast(`project:${projectId}`, 'status-update', { projectId, status, updatedAt: Date.now() }); } } // Client-side example (JavaScript/TypeScript) /* import io from 'socket.io-client'; const socket = io('http://localhost:3000/core', { transports: ['websocket'] }); // Join personal room socket.emit('joinPersonalRoom', 'user:123456'); socket.on('joinPersonalRoomResult', (result) => { console.log(result); // 'join room successfully' }); // Listen for personal notifications socket.on('personal-notification', (data) => { console.log('Notification:', data); }); // Listen for project updates socket.emit('joinPersonalRoom', 'project:789'); socket.on('status-update', (data) => { console.log('Project update:', data); }); // Send message to server socket.emit('msgToServer', 'Hello from client'); socket.on('msgToClient', (message) => { console.log('Server response:', message); }); */ ``` ## Pagination Helper Paginate query results with metadata for list endpoints. ```typescript import { Controller, Get, Query } from '@nestjs/common'; import { PaginationDto, paginate, ApiPaginatedResponse } from '@common'; import { ApiTags } from '@nestjs/swagger'; class UserDto { id: string; email: string; firstName: string; lastName: string; } @ApiTags('users') @Controller('users') export class UsersController { @Get() @ApiPaginatedResponse(UserDto) async getUsers(@Query() pagination: PaginationDto) { const { limit, page } = pagination; const offset = pagination.getOffset; // Calculates: limit * (page - 1) // Fetch data from database const users = await this.findUsers({ limit, offset }); const totalUsers = await this.countUsers(); // Return paginated response return paginate(users, totalUsers, page, limit); } private async findUsers(options: { limit: number; offset: number }) { // Database query implementation return [ { id: '1', email: 'user1@example.com', firstName: 'John', lastName: 'Doe' }, { id: '2', email: 'user2@example.com', firstName: 'Jane', lastName: 'Smith' } ]; } private async countUsers() { return 100; } } // Request: GET /api/v1/users?page=2&limit=10 // Response: /* { "message": "success", "data": [...], "meta": { "itemCount": 10, "totalItems": 100, "itemsPerPage": 10, "totalPages": 10, "currentPage": 2, "hasNextPage": true, "hasPrevPage": true, "prevPage": 1, "nextPage": 3 } } */ ``` ## Configuration Service Type-safe environment variable validation with class-validator. ```typescript import { Injectable } from '@nestjs/common'; import { ConfigService } from '@lib/configs'; import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; import { Transform } from 'class-transformer'; // Define custom config DTO class EmailConfig { @IsString() @IsNotEmpty() MAIL_HOST!: string; @Transform(({ value }) => +value) @IsNumber() MAIL_PORT!: number; @IsString() @IsNotEmpty() MAIL_USER!: string; @IsString() @IsNotEmpty() MAIL_PASSWORD!: string; @IsString() @IsNotEmpty() MAIL_FROM!: string; } @Injectable() export class EmailService { private readonly emailConfig: EmailConfig; constructor(private readonly configService: ConfigService) { // Validates environment variables on initialization this.emailConfig = this.configService.validate('EmailService', EmailConfig); } getMailConfig() { return { host: this.emailConfig.MAIL_HOST, port: this.emailConfig.MAIL_PORT, auth: { user: this.emailConfig.MAIL_USER, pass: this.emailConfig.MAIL_PASSWORD }, from: this.emailConfig.MAIL_FROM }; } // Access other environment variables getAppPort() { return this.configService.env.PORT; // Type-safe access } isProduction() { return this.configService.env.NODE_ENV === 'Production'; } } ``` ## Password Utilities Hash and verify passwords with bcrypt for secure authentication. ```typescript import { Injectable } from '@nestjs/common'; import { UTIL } from '@common'; @Injectable() export class AuthService { async registerUser(email: string, password: string, firstName: string, lastName: string) { // Hash password before storing const hashedPassword = await UTIL.createPassword(password); const user = { email, password: hashedPassword, firstName, lastName }; // Save to database // await this.userModel.create(user); return { message: 'User registered successfully' }; } async validateCredentials(email: string, password: string) { // Fetch user from database with password field const user = await this.findUserByEmail(email); if (!user) { throw new Error('User not found'); } // Verify password against stored hash const isValid = await UTIL.verifyPassword(password, user.password); if (!isValid) { throw new Error('Invalid credentials'); } return { id: user.id, email: user.email, roles: user.roles }; } private async findUserByEmail(email: string) { // Mock user - replace with actual database query return { id: '123', email: 'user@example.com', password: '$2a$11$abcdefghijklmnopqrstuvwxyz1234567890', // hashed password roles: ['USER'] }; } } ``` ## Mongoose Schema with Virtuals Define MongoDB schemas with type safety, indexes, and virtual properties. ```typescript import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, Types } from 'mongoose'; import { T } from '@common'; @Schema({ timestamps: true, collection: 'todos' }) export class Todo { @Prop({ required: true, trim: true, minlength: 1, maxlength: 200, index: true }) title!: string; @Prop({ trim: true, maxlength: 1000 }) description?: string; @Prop({ enum: Object.values(T.TodoStatus), default: T.TodoStatus.PENDING, index: true }) status!: T.TodoStatus; @Prop({ enum: Object.values(T.TodoPriority), default: T.TodoPriority.MEDIUM, index: true }) priority!: T.TodoPriority; @Prop({ type: Types.ObjectId, ref: 'User', required: true, index: true }) assignedTo!: Types.ObjectId; @Prop({ type: Date, index: true }) dueDate?: Date; @Prop({ type: Date, index: true }) completedAt?: Date; @Prop({ type: Number, min: 0, max: 100, default: 0 }) progress?: number; @Prop({ type: [String], default: [], index: true }) tags?: string[]; } export type TodoDocument = Todo & Document; export const TodoSchema = SchemaFactory.createForClass(Todo); // Virtual property: check if overdue TodoSchema.virtual('isOverdue').get(function (this: TodoDocument) { if (!this.dueDate || this.status === T.TodoStatus.COMPLETED) return false; return new Date() > this.dueDate; }); // Virtual property: days until due TodoSchema.virtual('daysUntilDue').get(function (this: TodoDocument) { if (!this.dueDate) return null; const diff = this.dueDate.getTime() - Date.now(); return Math.ceil(diff / (1000 * 60 * 60 * 24)); }); // Static method: find by user TodoSchema.statics.findByUser = function (userId: Types.ObjectId, includeCollaborations = false) { return this.find({ assignedTo: userId }); }; // Static method: find overdue tasks TodoSchema.statics.findOverdue = function () { return this.find({ dueDate: { $lt: new Date() }, status: { $nin: [T.TodoStatus.COMPLETED, T.TodoStatus.CANCELLED] } }); }; // Pre-save middleware: auto-set completedAt TodoSchema.pre('save', function (next) { if (this.isModified('status') && this.status === T.TodoStatus.COMPLETED) { this.completedAt = new Date(); } next(); }); // Usage example /* import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; @Injectable() export class TodoService { constructor(@InjectModel(Todo.name) private todoModel: Model) {} async createTodo(userId: string, title: string, dueDate: Date) { const todo = new this.todoModel({ title, assignedTo: userId, dueDate, status: T.TodoStatus.PENDING }); await todo.save(); // Access virtual properties console.log('Days until due:', todo.daysUntilDue); console.log('Is overdue:', todo.isOverdue); return todo; } async getOverdueTasks() { return this.todoModel.findOverdue(); } } */ ``` ## I18next Internationalization Translate API responses with type-safe translation keys. ```typescript import { Controller, Get, BadRequestException } from '@nestjs/common'; import { I18Next, i18n } from '@lib/i18next'; import { Translate } from '@common'; @Controller('products') export class ProductsController { // Inject i18next from request @Get('item') getProduct(@I18Next() i18n: i18n) { const product = this.findProduct('123'); if (!product) { throw new BadRequestException( i18n.t(Translate.ERROR_PRODUCT_NOT_FOUND) ); } return { message: i18n.t(Translate.SUCCESS_PRODUCT_RETRIEVED), data: product }; } @Get('create') createProduct(@I18Next() i18n: i18n) { // Translation with interpolation const productName = 'Laptop Pro'; return { message: i18n.t(Translate.SUCCESS_PRODUCT_CREATED, { name: productName }), data: { name: productName } }; } private findProduct(id: string) { return { id, name: 'Product 1', price: 99.99 }; } } // Client sends language in header: // GET /api/v1/products/item // Headers: { "x-language": "en" } or { "x-language": "zh" } // assets/locales/en/translation.json /* { "ERROR_PRODUCT_NOT_FOUND": "Product not found", "SUCCESS_PRODUCT_RETRIEVED": "Product retrieved successfully", "SUCCESS_PRODUCT_CREATED": "Product {{name}} created successfully" } */ // assets/locales/zh/translation.json /* { "ERROR_PRODUCT_NOT_FOUND": "产品未找到", "SUCCESS_PRODUCT_RETRIEVED": "成功获取产品", "SUCCESS_PRODUCT_CREATED": "成功创建产品 {{name}}" } */ ``` ## Logger Service Structured logging with Winston and daily file rotation. ```typescript import { Injectable } from '@nestjs/common'; import { LoggerService } from '@lib/logger/logger.service'; @Injectable() export class OrderService { constructor(private readonly logger: LoggerService) {} async createOrder(userId: string, items: any[]) { this.logger.log(`Creating order for user: ${userId}`); try { const order = await this.processOrder(userId, items); this.logger.log(`Order created successfully: ${order.id}`, { orderId: order.id, userId, itemCount: items.length, total: order.total }); return order; } catch (error) { this.logger.error(`Failed to create order for user ${userId}`, { userId, error: error.message, stack: error.stack }); throw error; } } async processPayment(orderId: string, amount: number) { this.logger.warn(`Processing high-value payment`, { orderId, amount, threshold: 10000 }); // Payment processing logic } private async processOrder(userId: string, items: any[]) { this.logger.debug(`Processing order items`, { count: items.length }); return { id: 'order-123', userId, items, total: 299.99, createdAt: new Date() }; } } // Logs are written to: // - Console (formatted for development) // - logs/error-%DATE%.log (only errors) // - logs/combined-%DATE%.log (all logs) // Files rotate daily and keep 14 days of history ``` ## Main Application Configuration Bootstrap NestJS application with global settings and Swagger documentation. ```typescript // src/main.ts import { NestFactory } from '@nestjs/core'; import { ValidationPipe, VersioningType } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import helmet from 'helmet'; import { AppModule } from './app.module'; import { ConfigService } from '@lib/configs'; import { LoggerService } from '@lib/logger/logger.service'; import { swaggerDescription, swaggerOptions } from '@common'; async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true }); const configService = app.get(ConfigService); const logger = app.get(LoggerService); // Global API prefix: /api app.setGlobalPrefix('api'); // URI versioning: /api/v1 app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' }); // Security headers app.use(helmet()); // CORS configuration const corsOrigin = configService.env.CORS_ORIGIN || '*'; app.enableCors({ origin: corsOrigin.split(','), credentials: true }); // Global validation with transformation app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true } }) ); // Swagger documentation at /api/docs const swaggerConfig = new DocumentBuilder() .setTitle('NestJS Enterprise Boilerplate') .setDescription(swaggerDescription) .setVersion('1.0') .addBearerAuth() .setExternalDoc('Postman Collection', '/docs-json') .build(); const document = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('api/docs', app, document, { swaggerOptions }); const port = configService.env.PORT; await app.listen(port); logger.log(`🚀 Application is running on: http://localhost:${port}/api`); logger.log(`📚 API Documentation: http://localhost:${port}/api/docs`); } bootstrap(); // Environment variables (.env) /* NODE_ENV=development PORT=3000 MONGO_DB_URI=mongodb://localhost:27017/nestjs_boilerplate REDIS_HOST=localhost REDIS_PORT=6379 REDIS_AUTH_PASS= RABBIT_MQ_HOST=amqp://guest:guest@localhost:5672 JWT_SECRET=your-super-secret-jwt-key JWT_REFRESH_SECRET=your-super-secret-refresh-key JWT_ACCESS_EXPIRATION=15m JWT_REFRESH_EXPIRATION=7d CORS_ORIGIN=http://localhost:3000,http://localhost:3001 RATE_LIMIT_TTL=60 RATE_LIMIT_MAX=100 */ ``` This NestJS boilerplate provides a complete foundation for building enterprise-grade applications with MongoDB persistence, real-time features via WebSockets, distributed messaging through RabbitMQ, and Redis caching. The modular architecture uses global modules for core services (database, JWT, Redis, i18next, RabbitMQ) making them available throughout the application without explicit imports. The consolidated API module pattern eliminates circular dependencies by keeping all feature services in a single module. Common integration patterns include: (1) REST API development with automatic Swagger documentation, type-safe DTOs with class-validator, and standardized response structures using pagination and success response helpers; (2) Real-time communication by injecting EventGateway to broadcast updates to connected WebSocket clients in personal or topic-based rooms; (3) Asynchronous processing using RabbitMQService to queue background jobs like emails, notifications, or data processing tasks; (4) Caching strategies with Redis for session management, API response caching, and distributed locking; (5) Authentication flows using JwtService for token generation and AuthenticateGuard for route protection with role-based access control. The project structure separates business logic (src/api), infrastructure (src/lib), domain models (src/schemas for MongoDB or src/entities for PostgreSQL), shared utilities (src/common), and real-time features (src/events), making it easy to scale and maintain as applications grow.