返回首页

NestJS 入门:依赖注入与模块化到底解决了什么问题

从 Controller、Provider、Module 三件套,到依赖注入(DI)和 IoC 容器,理解 Nest 为什么适合写中大型 Node 后端。

28 约 4 分钟 · 4634 字 后端

用 Express / Koa 写小项目很爽,但项目一大,你会发现满屏的 require、手动 new 出来的实例、到处传递的数据库连接…… 代码逐渐变成一团乱麻。

NestJS 就是来解决这个问题的。它借鉴了 Angular 和 Spring 的思想,把依赖注入(DI)模块化带进 Node 后端。这篇讲清它的核心三件套和 DI 到底好在哪。

三件套:Controller、Provider、Module

Nest 的世界由三种基本角色构成,理解它们就理解了 Nest 的骨架。

Controller(控制器):负责接收请求、返回响应,只做"路由 + 参数 + 调用 service",不写业务逻辑:

import { Controller, Get, Param } from '@nestjs/common'
import { PostsService } from './posts.service'

@Controller('posts')
export class PostsController {
  // 构造函数里"声明"我需要 PostsService,Nest 自动塞进来
  constructor(private readonly postsService: PostsService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.postsService.findById(Number(id))
  }
}

Provider(提供者 / 服务):真正干活的地方,业务逻辑都在这。用 @Injectable() 标记它"可被注入":

import { Injectable } from '@nestjs/common'

@Injectable()
export class PostsService {
  findById(id: number) {
    // 真正的业务逻辑、数据库查询写在这里
    return { id, title: '示例文章' }
  }
}

Module(模块):把相关的 Controller 和 Provider 组织在一起,声明依赖关系:

import { Module } from '@nestjs/common'
import { PostsController } from './posts.controller'
import { PostsService } from './posts.service'

@Module({
  controllers: [PostsController],
  providers: [PostsService],
  exports: [PostsService], // 导出后,别的模块才能用它
})
export class PostsModule {}

什么是依赖注入(DI)

注意上面 PostsController 的构造函数:

constructor(private readonly postsService: PostsService) {}

我们没有 new PostsService(),只是"声明"了一句"我需要一个 PostsService"。Nest 在启动时自动帮你创建实例并"注入"进来 —— 这就是依赖注入

对比一下传统写法的痛点:

// ❌ 传统方式:自己 new,依赖关系硬编码
class PostsController {
  private postsService: PostsService
  constructor() {
    // 如果 PostsService 又依赖 Database、Logger...
    // 你得在这里一层层手动 new,改一处动全身
    const db = new Database(config)
    const logger = new Logger()
    this.postsService = new PostsService(db, logger)
  }
}

依赖一旦多起来、嵌套起来,手动 new 就是噩梦。而且测试时想换成 mock 也极其麻烦。

DI 把"创建依赖"这件事从你手里拿走,交给一个叫 **IoC 容器(控制反转容器)**的东西统一管理。你只管声明"我要什么",容器负责"给你什么"。

控制反转(IoC)是什么意思

"控制反转"听着玄,其实就一句话:对象创建和依赖装配的控制权,从你的代码反转给了框架

  • 传统方式:你的代码主动 new 出依赖,控制权在你手里
  • IoC 方式:你只声明需求,框架(容器)在背后创建好一切并装配给你

带来的好处是实打实的:

  1. 解耦:Controller 不关心 PostsService 怎么被创建,换实现不用改 Controller
  2. 单例复用:Nest 默认把 Provider 作为单例管理,整个应用共用一个实例,省内存
  3. 易测试:测试时用容器把真实 Provider 替换成 mock,一行配置搞定

怎么替换实现:Provider 的高级用法

DI 的威力在"替换"时才完全显现。Nest 允许你用 token 自定义提供什么:

@Module({
  providers: [
    // 用一个值充当 Provider
    { provide: 'CONFIG', useValue: { db: 'mysql://...' } },

    // 用工厂函数动态创建
    {
      provide: 'DB_CONNECTION',
      useFactory: async () => {
        return await createConnection(/* ... */)
      },
    },

    // 接口换实现:把 PostsService 偷偷换成另一个类
    { provide: PostsService, useClass: MockPostsService },
  ],
})
export class AppModule {}

注入自定义 token 用 @Inject:

constructor(@Inject('CONFIG') private config: AppConfig) {}

这种能力让"测试环境用内存数据库、生产环境用真 MySQL"这类需求,变成了改一行配置的事。

模块化:大型项目的组织方式

随着功能变多,Nest 鼓励你按业务域拆模块:UsersModulePostsModuleAuthModule…… 每个模块自己管自己的 Controller 和 Service,通过 imports / exports 互相协作:

@Module({
  imports: [PostsModule, UsersModule, AuthModule], // 组合子模块
})
export class AppModule {}
// PostsModule 想用 UsersService,就 import UsersModule
@Module({
  imports: [UsersModule], // 前提:UsersModule 里 exports 了 UsersService
  controllers: [PostsController],
  providers: [PostsService],
})
export class PostsModule {}

这种"高内聚、低耦合"的边界,让大型项目即使有几十个模块也能保持清晰,新人也容易找到代码该写在哪。

请求生命周期:不只是中间件

Nest 在中间件之外,还提供了一套更细粒度的"切面"工具,各管一段:

机制 职责 典型用途
Middleware 最外层,贴近原生 日志、CORS
Guard 路由能不能进 鉴权、权限
Interceptor 包裹前后,可改返回值 统一响应格式、缓存、耗时统计
Pipe 处理入参 参数校验、类型转换
Exception Filter 兜住异常 统一错误响应

这套分工比"一把梭中间件"更清晰。比如鉴权用 Guard、参数校验用 Pipe、统一返回结构用 Interceptor,各司其职。

该不该用 Nest

给个实在的判断:

  • 适合用:中大型项目、多人协作、业务模块多、需要长期维护、团队熟悉 TypeScript
  • 不必用:几个接口的小工具、个人脚手架、追求极致轻量。这种场景 Express / Koa 反而更省事

Nest 的代价是前期概念多、模板代码多,但换来的是大项目里的可维护性和一致性。这是一笔值不值得的权衡,取决于你的项目规模。

小结

  • Nest 三件套:Controller 管路由、Provider 管业务、Module 管组织
  • 依赖注入:你只声明"要什么",IoC 容器负责创建并装配,告别手动 new
  • 控制反转:对象创建权从你的代码交给框架,换来解耦、单例复用、易测试
  • Provider 支持 useValue / useFactory / useClass,轻松替换实现做 mock
  • 按业务域拆 Module,靠 imports/exports 协作,大项目也清晰
  • Guard / Interceptor / Pipe / Filter 各管一段,比裸中间件分工更细
  • 中大型、多人、长期维护的项目值得上 Nest;小工具用 Express/Koa 更轻

理解了 DI 和模块化,你会发现 Nest 不是"更复杂的 Express",而是一套帮你把大项目管理得井井有条的工程化方案。