《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 项目会更加健壮、易维护且性能更高!🚀

相关推荐
Jinuss29 分钟前
代码质量管理工具-SonarQube
前端·代码规范
晓杰'1 天前
从0到1实现 Balatro 游戏后端(1):项目规划与牌型判断实现
后端·websocket·typescript·node.js·游戏开发·项目实战·nestjs
Csvn1 天前
实用的 AI 辅助编程技巧和最佳实践
人工智能·代码规范
用户5757303346241 天前
路由守卫 守卫住网站的安全 也守住我们的幸福
nestjs
Supersist2 天前
【设计模式03】使用模版模式+责任链模式优化实战
后端·设计模式·代码规范
jump_jump4 天前
把一份前端 checklist 变成 AI 的 Skill:让 CR 不再靠记忆
性能优化·ai编程·代码规范
jump_jump4 天前
TSRX:一份源码,编译到 React / Solid / Vue / Preact / Ripple
前端框架·代码规范·编译器
ZJY1324 天前
2-1:在NestJS中使用mikro-orm
后端·nestjs
这个DBA有点耶6 天前
联合索引的顺序:写错等于白建(最左前缀+范围条件+覆盖索引详解)
数据库·代码规范
这个DBA有点耶7 天前
一张5000万行的表,加索引从45秒到0.02秒——索引设计你真的会吗
程序员·代码规范