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

相关推荐
一只拉古4 小时前
使用正则表达式解决问题:从 LeetCode 到生产环境
后端·正则表达式·代码规范
Aibo0071 天前
如何让Agent开发正真可控、可靠? Cursor AI工程化
代码规范·cursor
矩阵科学1 天前
重构艺术 | 内联与查询替代临时变量
重构·代码规范·代码重构·内联临时变量·查询替代变量
江湖人称菠萝包1 天前
【Code】《代码整洁之道》笔记-Chapter16-重构SerialDate
代码规范
汪小成1 天前
NestJS学习笔记-02-模块、控制器与服务,手把手构建你的第一个CRUD API!🚀
后端·nestjs
汪小成2 天前
NestJS学习笔记-01-第一个Nest应用诞生记 🚀
后端·nestjs
plusone2 天前
【Nest指北系列】守卫
nestjs
用户11481867894843 天前
大文件下载、断点续传功能
前端·nestjs
hezf3 天前
初识 Prisma-结合NestJS
数据库·后端·nestjs
pe7er3 天前
代码风格指南:优先处理 Default、空值、异常值(开始主逻辑之前,先处理所有可能出错的情况)
代码规范