NestJS 是一个强大的 TypeScript 框架,适用于构建可扩展的企业级应用。但如果你是新手,可能会遇到各种坑,比如依赖注入错误、生命周期管理不当、性能优化不足等。本文将总结 常见的坑 及其 最佳实践,帮助你避免踩雷,提高开发效率。
1. 依赖注入 (DI) 坑
❌ 问题:服务无法注入,Nest 报错 "Nest can't resolve dependencies"
✅ 解决方案:正确注册 Provider
在 NestJS 中,服务 (Service) 必须先注册到 providers
才能在模块 (Module) 内使用,否则 Nest 无法解析依赖。
错误示例
typescript
typescript
复制编辑
@Injectable()
export class UserService {
constructor(private readonly repo: UserRepository) {} // 可能报错
}
正确示例
ruby
typescript
复制编辑
@Module({
providers: [UserService, UserRepository], // 确保所有依赖都注册
exports: [UserService], // 如果其他模块需要用到
})
export class UserModule {}
如果 UserRepository
是一个 @Injectable()
的类,确保它被正确提供 (providers
),否则 NestJS 解析不到依赖,会报错。
2. 生命周期管理
❌ 问题:数据库连接未正确关闭,导致资源泄漏
NestJS 允许使用 onModuleDestroy
和 onApplicationShutdown
进行清理。
正确示例
typescript
typescript
复制编辑
@Injectable()
export class DatabaseService implements OnModuleDestroy, OnApplicationShutdown {
private connection: any;
async connect() {
this.connection = await createDatabaseConnection();
}
async onModuleDestroy() {
console.log('Module is being destroyed, closing DB connection');
await this.connection.close();
}
async onApplicationShutdown() {
console.log('Application shutting down, closing DB connection');
await this.connection.close();
}
}
使用 onModuleDestroy
处理 模块销毁 ,使用 onApplicationShutdown
处理 整个应用关闭 时的资源释放。
3. @Inject()
依赖注入
❌ 问题:自定义 Token 依赖无法解析
NestJS 允许使用 @Inject()
解决 Token 注入问题,特别是 useFactory
的情况。
错误示例
typescript
typescript
复制编辑
@Injectable()
export class SomeService {
constructor(private readonly config: ConfigService) {} // 可能报错
}
正确示例
less
typescript
复制编辑
@Injectable()
export class SomeService {
constructor(@Inject('CONFIG_SERVICE') private readonly config: ConfigService) {}
}
@Module({
providers: [
{
provide: 'CONFIG_SERVICE',
useClass: ConfigService,
},
SomeService,
],
})
export class AppModule {}
如果 ConfigService
是动态提供的,必须用 @Inject('TOKEN')
形式注入。
4. 事务管理
❌ 问题:数据库事务无法正确回滚
在 NestJS 中,使用事务时,确保所有操作都在 同一个事务上下文 内执行。
正确示例
typescript
typescript
复制编辑
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async createUser(data: CreateUserDto) {
return await this.prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data });
await tx.profile.create({ data: { userId: user.id } });
return user;
});
}
}
⚠️ 不要混用事务外的 this.prisma
和 tx
,否则事务可能失效。
5. 全局模块 & 作用域
❌ 问题:全局模块没有正确导出,导致服务无法注入
错误示例
kotlin
typescript
复制编辑
@Module({
providers: [ConfigService],
})
export class ConfigModule {}
这样 ConfigService
只能在 ConfigModule
内使用,其他模块无法访问。
正确示例
less
typescript
复制编辑
@Module({
providers: [ConfigService],
exports: [ConfigService], // 必须 export 才能让其他模块使用
})
export class ConfigModule {}
@Module({
imports: [ConfigModule],
})
export class AppModule {}
如果 ConfigModule
需要在整个应用可用:
kotlin
typescript
复制编辑
@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
这样 ConfigService
在任何地方都可以用,不需要手动 imports
。
6. 性能优化
❌ 问题:API 响应慢,CPU 负载高
✅ 解决方案
-
使用缓存:减少数据库查询压力。
-
避免同步阻塞 :不要在
async
方法里使用sync
操作,例如:javascripttypescript 复制编辑 async getData() { const data = fs.readFileSync('./large-file.json'); // ❌ 阻塞操作 return JSON.parse(data); }
改成:
javascripttypescript 复制编辑 async getData() { const data = await fs.promises.readFile('./large-file.json'); return JSON.parse(data); }
-
优化数据库查询:
phptypescript 复制编辑 const users = await prisma.user.findMany({ include: { posts: true }, });
只查询需要的字段:
phptypescript 复制编辑 const users = await prisma.user.findMany({ select: { id: true, name: true }, });
-
开启 gzip 压缩:
javascripttypescript 复制编辑 import * as compression from 'compression'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(compression()); await app.listen(3000); } bootstrap();
7. 任务调度(避免内存泄漏)
❌ 问题:定时任务导致应用内存不断增长
✅ 解决方案
如果你使用 setInterval
或 schedule
来执行定时任务,确保清理任务,否则可能会造成内存泄漏。
错误示例
javascript
typescript
复制编辑
setInterval(() => {
console.log('Running task...');
}, 1000);
没有办法在应用关闭时清除。
正确示例
typescript
typescript
复制编辑
import { Injectable, OnModuleDestroy } from '@nestjs/common';
@Injectable()
export class TaskService implements OnModuleDestroy {
private interval: NodeJS.Timeout;
startTask() {
this.interval = setInterval(() => {
console.log('Running task...');
}, 1000);
}
onModuleDestroy() {
clearInterval(this.interval);
}
}
使用 clearInterval
在模块销毁时清理任务。
总结
⚠️ 常见坑 | ✅ 最佳实践 |
---|---|
依赖注入错误 | 确保所有 providers 都被注册 |
生命周期管理 | 使用 onModuleDestroy 和 onApplicationShutdown 释放资源 |
@Inject() 解析失败 |
使用 useClass 或 useFactory 绑定 Token |
数据库事务失败 | 事务范围内不要混用不同的 DB 实例 |
模块作用域问题 | @Global() 或者 exports: [...] 让服务在其他模块可用 |
API 性能低 | 使用缓存、优化查询、避免同步阻塞 |
定时任务导致内存泄漏 | 适时 clearInterval 释放资源 |
按照这些最佳实践,你的 NestJS 项目会更加健壮、易维护且性能更高!🚀