《NestJS 避坑指南:常见问题与最佳实践》

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 允许使用 onModuleDestroyonApplicationShutdown 进行清理。

正确示例

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.prismatx,否则事务可能失效。


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 操作,例如:

    javascript 复制代码
    typescript
    复制编辑
    async getData() {
      const data = fs.readFileSync('./large-file.json'); // ❌ 阻塞操作
      return JSON.parse(data);
    }

    改成:

    javascript 复制代码
    typescript
    复制编辑
    async getData() {
      const data = await fs.promises.readFile('./large-file.json');
      return JSON.parse(data);
    }
  • 优化数据库查询

    php 复制代码
    typescript
    复制编辑
    const users = await prisma.user.findMany({
      include: { posts: true },
    });

    只查询需要的字段

    php 复制代码
    typescript
    复制编辑
    const users = await prisma.user.findMany({
      select: { id: true, name: true },
    });
  • 开启 gzip 压缩

    javascript 复制代码
    typescript
    复制编辑
    import * as compression from 'compression';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.use(compression());
      await app.listen(3000);
    }
    bootstrap();

7. 任务调度(避免内存泄漏)

❌ 问题:定时任务导致应用内存不断增长

✅ 解决方案

如果你使用 setIntervalschedule 来执行定时任务,确保清理任务,否则可能会造成内存泄漏。

错误示例

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 都被注册
生命周期管理 使用 onModuleDestroyonApplicationShutdown 释放资源
@Inject() 解析失败 使用 useClassuseFactory 绑定 Token
数据库事务失败 事务范围内不要混用不同的 DB 实例
模块作用域问题 @Global() 或者 exports: [...] 让服务在其他模块可用
API 性能低 使用缓存、优化查询、避免同步阻塞
定时任务导致内存泄漏 适时 clearInterval 释放资源

按照这些最佳实践,你的 NestJS 项目会更加健壮、易维护且性能更高!🚀

相关推荐
天涯浪客20 小时前
我的漏洞扫描器,把被测项目的源码写坏了
代码规范
事圆则缓1 天前
遗留项目改造实战指南:从可读性、代码质量到性能和 AI 约束
android·ai·代码规范
乱码三千1 天前
开发了一套AI智能体协作知识体系
人工智能·架构·代码规范
梦梦代码精3 天前
《回收租赁系统技术选型避坑指南:业务闭环与二开自由度详解》
开发语言·低代码·docker·开源·代码规范
这个DBA有点耶4 天前
临时表从4.7秒到0.12秒:不是所有Using temporary都需要优化
数据库·mysql·代码规范
泛联新安4 天前
“天神”空降:泛联新安受邀参加第八届“纵横”网络空间创新论坛
网络安全·自动化·代码规范·漏洞挖掘·代码安全·代码漏洞扫描·ai+可信开发
用户970161501684 天前
浏览器切到后台后,AI 给我的代码就失效了:我拿 3 个模型测了 6 个定时器坑
前端·代码规范
RunProof可证工程5 天前
AI 写的登录接口能跑,上线前我却查出 SQL 注入写法
sql·代码规范
咖啡八杯5 天前
实体基类设计:BaseEntity 公共字段抽取与 TreeEntity 树形继承
java·架构·代码规范
这个DBA有点耶7 天前
MySQL 8.0执行计划分析利器:EXPLAIN ANALYZE到底比EXPLAIN强在哪?
数据库·mysql·代码规范