nestjs(1)-模块的相互调用

32 阅读5分钟

新增user模块

新增usermodule

nest g mo user

image.png

新增usercontroller

nest g co user

image.png

新增endpoint返回内容

image.png

新增service

nest g s user

image.png

在controller中调用service

image.png

新建role模块,并在user中使用

先生成对应的modulecontrollerservice

nest g mo role
nest g co role
nest g s role

先在module中导出

直接在user.controller中调用role.service

提示报错

image.png

修复

image.png

解释

用户问的是一个关于 NestJS 依赖注入和模块系统的概念性问题。让我先调用 ExperienceRecall 看看有没有相关的经验,然后用简单的方式解释清楚这个机制。

这个问题涉及 NestJS 的核心概念:

  1. Module 是组织代码的基本单元
  2. exports 是模块对外"暴露"的东西
  3. imports 是模块要"使用"的其他模块

让我先看看 ExperienceRecall 里有没有相关的经验,然后给出解释。好的,我已经获取了相关经验。现在让我用简单清晰的方式解释用户的问题。

用户的核心疑问是:为什么 RoleModule exports 的是 RoleService,但 UserModule imports 的却是 RoleModule(不是 RoleService)?

这是 NestJS 模块系统的核心设计:

  1. Module 是"容器",Service 是容器里的"物品"
  2. exports = 把容器里的某些物品"对外可借用"
  3. imports = 告诉 DI 容器"我需要从哪个容器里借东西"

所以流程是:

  • RoleModule 在自己的 exports 里说:"我里面的 RoleService 是对外可借用的"
  • UserModule 在自己的 imports 里说:"我需要 RoleModule 这个容器"
  • NestJS DI 容器看到 imports: [RoleModule] 后,会去 RoleModule 里看 exports 了哪些东西
  • 然后把 RoleService 注入到 UserController 的构造函数中

让我用一个生活化的比喻来解释。 这是 NestJS 模块系统的核心设计,让我用一个清晰的方式帮你理解:


🏠 生活化比喻

NestJS 概念类比作用
Module一栋房子组织代码的"容器"
providers房子里的家具本模块内部使用的东西
exports从房子里搬到门口的家具对外可借用的家具
imports写下"我需要 X 房子"告诉 DI 容器要从哪栋房子借东西

🔍 你的代码对应关系

RoleModule (房子)
 ├── providers: [RoleService]    ← 房子里的家具
 └── exports:  [RoleService]    ← 把这件家具搬到门口 → "对外可借"

UserModule (另一栋房子)
 └── imports: [RoleModule]      ← 写下"我需要 RoleModule 这栋房子"
                                ← NestJS 容器自动去 RoleModule 门口看
                                ← 发现 RoleService 在那里,就借给你

为什么 imports 的是 Module 而不是 Service?

因为 NestJS 的 DI 容器是按 Module 为单位 组织依赖关系的。它的工作流程是:

  1. 看到 imports: [RoleModule] → 知道要找 RoleModule 这个模块
  2. 进入 RoleModule → 查看它的 exports 数组
  3. 把 exports 里声明的 Provider(这里是 RoleService)注册到当前模块的可见范围
  4. 这样 UserController 构造函数里写 private roleService: RoleService 就能注入成功

❓ 如果 imports 直接写 Service 会怎样?

// ❌ 不允许!NestJS 会报错
imports: [RoleService]

因为 Service 只是一个普通的 TypeScript 类,它没有告诉 NestJS"我是谁、我属于哪个模块"。而 Module 是有明确元数据(@Module() 装饰器)的,DI 容器知道怎么找到它、解析它的导出。


✅ 一句话总结

exports 是"我家里有什么东西可以借",imports 是"我需要去哪栋房子借"——你不能直接借一件家具,但可以说"我需要那栋房子,然后让房子的主人把东西拿给你"。

这种设计的好处是:一个 Module 可以 exports 多个 Provider(Service、Pipe、Guard 等),使用方只需一次 imports 就能获得所有可借的东西,解耦且清晰。

想在user.controller中使用app.service

直接使用会循环引用的错

image.png

修复

image.png

AppModule ──imports──▶ UserModule
   ▲                       │
   └────imports────────────┘   ← 循环!

用户现在遇到的问题是典型的 NestJS 循环依赖:AppModule 里 imports 了 UserModule,而 UserModule 又 imports 了 AppModule,这样会形成循环导入导致 NestJS 报错。

🔑 核心矛盾:根模块不应被反向依赖

AppModule根模块(Root Module),它的职责是"组装"其他模块,而不是提供业务 Service 给子模块用。如果子模块需要某个 Service,正确的做法是把那个 Service 抽出来,放到一个独立的共享模块里,让两边都能 import 它。


✅ 解决方案:抽出 SharedModule 专门放共享的 AppService

抽 SharedModule 的方案。让我规划一下具体要做的事情:

  1. 创建 src/shared/shared.module.ts —— SharedModule,提供并导出 AppService
  2. 修改 src/app.module.ts —— 移除 providers/exports 里的 AppService,改为 imports SharedModule
  3. 修改 src/user/user.module.ts —— 移除 imports 里的 AppModule,改为 imports SharedModule

✅ 搞定!循环依赖已解决,项目启动正常

改了什么?

Before(循环依赖)                          After(无循环)
┌──────────┐                              ┌──────────────┐
│ AppModule │ ──imports──▶ UserModule      │  SharedModule│
│  └exports │◀──imports─────┘  ❌ 循环!    │ providers:   │
│  AppService                              │  AppService  │
└──────────┘                              │ exports:     │
                                          │  AppService  │
                                          └──────┬───────┘
                                                 ▲
                           ┌────────── imports ──┤
                           │                     │
                    ┌──────┴──────┐       ┌──────┴──────┐
                    │ AppModule   │       │ UserModule  │
                    └─────────────┘       └─────────────┘

文件变更一览

文件改动
shared.module.ts新建 — 注册并 exports: [AppService]
app.module.ts移除 providers/exports: AppService,改为 imports: [SharedModule]
user.module.ts移除 imports: AppModule,改为 imports: [SharedModule]

关键理解

根模块(AppModule)是组装层,不能被反向依赖。 当多个子模块需要共享同一个 Service 时,应该把它抽到一个"第三方共享模块"里,让大家都 import 它,而不是让子模块 import 根模块。

在app.controller中使用user.service

可以直接使用

image.png

📌 为什么这个场景不需要改任何代码就能生效?

和上一个问题对比一下:

上一个问题(AppService 给 UserModule 用)这个问题(UserService 给 AppModule 用)
依赖方向AppModule → UserModule 要互相借东西 ❌ 循环AppModule → UserModule ✅ 单向
解决方式抽 SharedModule 打破循环UserModule 直接 exports 就行
复杂度高(需要重构)低(只需加一行 exports)

你现在的 user.module.ts 第 11 行已经有 exports: [UserService],所以自动生效了。


🔑 一句话总结

单向依赖时,只要"被依赖的模块"把 Service 加到 exports 数组里,"依赖方"通过已有的 imports 就能直接用。只有双向依赖(循环)时,才需要抽第三方共享模块或用 forwardRef