《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 小时前
通俗易懂动态表单自定义字段解决方案
代码规范
tech_zjf1 天前
装饰器:给你的代码穿上品如的衣服
前端·typescript·代码规范
Andy.Zeng1 天前
Android StrictMode 使用与原理深度解析
android·性能优化·kotlin·代码规范·内存泄漏·strictmode·耗时检测
vker1 天前
责任链 vs 规则树:如何构建更强大的规则引擎?
后端·设计模式·代码规范
hikits2 天前
学会ES6解构用法,代码量能省一半
前端·javascript·代码规范
求知若饥2 天前
NestJS 项目实战-权限管理系统开发(十)
后端·node.js·nestjs
轻松Ai享生活2 天前
像哪吒一样在职业天际线上吒叱风云 - 向高级程序员学习编码之道
人工智能·代码规范·敏捷开发
京东云开发者2 天前
一种极简单的SpringBoot单元测试方法
代码规范
妖孽白YoonA3 天前
NestJS + DrizzleORM:轻量级、高性能的完美搭配? 🚀
orm·nestjs