NestJS Performance Optimization Explained
Performance Optimization Overview
Performance optimization is key to building high-performance NestJS applications. Through proper architecture design, code optimization, and resource management, you can significantly improve application response speed and throughput.
1. Database Optimization
Query Optimization
Use Indexes
typescript@Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Index() @Column() email: string; @Index() @Column() username: string; @Column() name: string; }
Avoid N+1 Queries
typescript// Bad way - N+1 queries async getUsersWithOrders() { const users = await this.userRepository.find(); for (const user of users) { user.orders = await this.orderRepository.find({ where: { userId: user.id } }); } return users; } // Good way - Use JOIN async getUsersWithOrders() { return this.userRepository.find({ relations: ['orders'], }); }
Use Pagination
typescriptasync findAll(page: number = 1, limit: number = 10) { const [data, total] = await this.userRepository.findAndCount({ skip: (page - 1) * limit, take: limit, }); return { data, total, page, totalPages: Math.ceil(total / limit), }; }
Connection Pool Configuration
typescriptTypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'test', entities: [User], extra: { connectionLimit: 20, // Adjust based on server configuration }, })
2. Caching Strategies
Use Redis Cache
typescriptimport { Injectable, Inject } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; @Injectable() export class UserService { constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} async findOne(id: number) { const cacheKey = `user_${id}`; const cachedUser = await this.cacheManager.get(cacheKey); if (cachedUser) { return cachedUser; } const user = await this.userRepository.findOne({ where: { id } }); await this.cacheManager.set(cacheKey, user, 3600); // Cache for 1 hour return user; } }
HTTP Caching
typescriptimport { Controller, Get, Header } from '@nestjs/common'; @Controller('users') export class UsersController { @Get() @Header('Cache-Control', 'public, max-age=300') // Cache for 5 minutes findAll() { return this.usersService.findAll(); } }
Interceptor Caching
typescriptimport { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class CacheInterceptor implements NestInterceptor { private cache = new Map(); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const cacheKey = request.url; if (this.cache.has(cacheKey)) { return of(this.cache.get(cacheKey)); } return next.handle().pipe( tap(data => { this.cache.set(cacheKey, data); }), ); } }
3. Asynchronous Processing
Use Async/Await
typescript// Good way async findAll() { return this.userRepository.find(); } // Bad way - Synchronous blocking findAll() { return this.userRepository.findSync(); }
Parallel Processing
typescript// Bad way - Serial execution async getUserData(userId: number) { const user = await this.userRepository.findOne({ where: { id: userId } }); const orders = await this.orderRepository.find({ where: { userId } }); const notifications = await this.notificationRepository.find({ where: { userId } }); return { user, orders, notifications }; } // Good way - Parallel execution async getUserData(userId: number) { const [user, orders, notifications] = await Promise.all([ this.userRepository.findOne({ where: { id: userId } }), this.orderRepository.find({ where: { userId } }), this.notificationRepository.find({ where: { userId } }), ]); return { user, orders, notifications }; }
Use Queue for Time-Consuming Tasks
typescriptimport { Injectable } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bull'; import { Queue } from 'bull'; @Injectable() export class EmailService { constructor(@InjectQueue('email') private emailQueue: Queue) {} async sendEmail(to: string, subject: string, body: string) { await this.emailQueue.add('send-email', { to, subject, body }); } }
4. Compression
Enable Gzip Compression
typescriptimport compression from 'compression'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(compression()); await app.listen(3000); }
Configure Compression Level
typescriptapp.use(compression({ level: 6, // Compression level 1-9 threshold: 1024, // Only compress responses larger than 1KB }));
5. Static Resource Optimization
Use CDN
typescriptimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Configure static resources to use CDN app.useStaticAssets('public', { prefix: '/static/', cacheControl: true, maxAge: 31536000, // 1 year }); await app.listen(3000); }
Image Optimization
typescriptimport { Controller, Get, Param, Res } from '@nestjs/common'; import { Response } from 'express'; import sharp from 'sharp'; @Controller('images') export class ImageController { @Get(':filename') async getImage(@Param('filename') filename: string, @Res() res: Response) { const image = sharp(`./public/images/${filename}`); // Optimize image based on request parameters const width = parseInt(req.query.width) || 800; const height = parseInt(req.query.height) || 600; image .resize(width, height) .jpeg({ quality: 80 }) .pipe(res); } }
6. Code Optimization
Use Lazy Loading Modules
typescript@Module({ imports: [ // Lazy load modules UsersModule, OrdersModule, ], }) export class AppModule {}
Avoid Unnecessary Calculations
typescript// Bad way async processUsers(users: User[]) { return users.map(user => { const expensiveResult = this.expensiveCalculation(user); return { ...user, result: expensiveResult }; }); } // Good way - Use caching async processUsers(users: User[]) { const cache = new Map(); return users.map(user => { const cacheKey = user.id; if (!cache.has(cacheKey)) { cache.set(cacheKey, this.expensiveCalculation(user)); } return { ...user, result: cache.get(cacheKey) }; }); }
Use Streams for Large Data
typescriptimport { Controller, Get, StreamableFile } from '@nestjs/common'; import { createReadStream } from 'fs'; @Controller('files') export class FileController { @Get('download/:filename') downloadFile(@Param('filename') filename: string): StreamableFile { const file = createReadStream(`./files/${filename}`); return new StreamableFile(file); } }
7. Monitoring and Analysis
Performance Monitoring
typescriptimport { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(LoggingInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const now = Date.now(); const request = context.switchToHttp().getRequest(); return next.handle().pipe( tap(() => { const duration = Date.now() - now; this.logger.log( `${request.method} ${request.url} - ${duration}ms`, ); }), ); } }
Use APM Tools
typescriptimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { Agent } from '@elastic/apm-node'; const apm = new Agent({ serviceName: 'nestjs-app', serverUrl: 'http://localhost:8200', }); async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
8. Load Balancing
Use PM2 Cluster Mode
bashnpm install pm2 -g pm2 start dist/main.js -i max --name nestjs-app
Configure PM2
javascript// ecosystem.config.js module.exports = { apps: [{ name: 'nestjs-app', script: './dist/main.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3000, }, }], };
9. Memory Optimization
Avoid Memory Leaks
typescript// Bad way - May cause memory leaks @Injectable() export class CacheService { private cache = new Map(); set(key: string, value: any) { this.cache.set(key, value); } } // Good way - Use TTL @Injectable() export class CacheService { private cache = new Map(); private ttl = 3600000; // 1 hour set(key: string, value: any) { this.cache.set(key, { value, expires: Date.now() + this.ttl, }); // Periodically clean up expired cache this.cleanup(); } private cleanup() { const now = Date.now(); for (const [key, data] of this.cache.entries()) { if (data.expires < now) { this.cache.delete(key); } } } }
Use Object Pool
typescript@Injectable() export class ObjectPool<T> { private pool: T[] = []; private factory: () => T; constructor(factory: () => T, size: number = 10) { this.factory = factory; for (let i = 0; i < size; i++) { this.pool.push(factory()); } } acquire(): T { return this.pool.pop() || this.factory(); } release(obj: T): void { this.pool.push(obj); } }
10. Network Optimization
Use HTTP/2
typescriptimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { NestExpressApplication } from '@nestjs/platform-express'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); await app.listen(3000); } bootstrap();
Configure Keep-Alive
typescriptasync function bootstrap() { const app = await NestFactory.create(AppModule); const server = app.getHttpServer(); server.keepAliveTimeout = 65000; server.headersTimeout = 66000; await app.listen(3000); }
Performance Optimization Best Practices
- Monitor Performance: Continuously monitor application performance metrics
- Benchmarking: Establish performance baselines and test regularly
- Progressive Optimization: Optimize gradually, one aspect at a time
- Code Review: Regularly review code to discover performance issues
- Use Caching: Reasonably use caching to reduce database queries
- Asynchronous Processing: Use async and parallel processing to improve efficiency
- Resource Compression: Enable compression to reduce data transfer
- Load Balancing: Use load balancing to distribute request pressure
- Regular Cleanup: Regularly clean up cache and temporary data
- Documentation: Record optimization process and results
Summary
NestJS performance optimization provides:
- Database query optimization
- Multiple caching strategies
- Asynchronous processing capabilities
- Resource compression techniques
- Monitoring and analysis tools
Mastering performance optimization is key to building high-performance NestJS applications. By properly applying various optimization techniques and best practices, you can significantly improve application performance, response speed, and user experience. Performance optimization is a continuous process that requires constant adjustment and improvement based on actual application scenarios.