【NestJS】18 项目架构全景图:从零散知识到完整体系

3120 字
16 分钟
【NestJS】18 项目架构全景图:从零散知识到完整体系

把学到的所有知识点串起来,建立完整的认知体系。不讲新东西,只帮你理清思路。

前言 ✨#

学完了 NestJS 的各个知识点,你可能会有这样的感觉:

  • 🤔 每个概念都懂,但不知道怎么组合在一起
  • 🤔 写代码时不知道该在哪个文件里写
  • 🤔 看别人的项目,感觉很复杂,不知道从哪里看起

这很正常。学习技术就像拼图,你需要先看到完整的画面,才知道每块拼图应该放在哪里。

这篇文章不讲新东西,只做一件事:把零散的知识点串成一条线,帮你建立完整的知识体系。

💡 版本说明:NestJS 11.0.1 + Node.js 20+

让我们开始吧!


一、项目的骨架:从入口到模块 🏗️#

1.1 应用的起点:main.ts#

想象你在建一栋楼,main.ts 就是地基。所有东西都从这里开始。

它做了什么?

1. 创建应用实例(NestFactory.create)
2. 配置全局功能
- ValidationPipe:验证所有输入数据
- CORS:允许跨域请求
- 全局拦截器:统一响应格式
- 全局过滤器:统一错误处理
3. 启动服务器(listen(3000))

关键理解

  • main.ts 是整个应用的”总开关”
  • 在这里配置的东西,对所有接口都生效
  • 你不需要在每个接口里重复配置

💡 类比:就像你家的总电闸,控制整个房子的电。


1.2 应用的大脑:AppModule#

如果 main.ts 是地基,那 AppModule 就是建筑的主体结构。

它的三个核心职责

1. 全局配置(imports)

ConfigModule → 管理环境变量(API Key、数据库地址)
MongooseModule → 连接数据库
JwtModule → 配置 JWT 认证
PassportModule → 配置认证策略

2. 功能模块(imports)

UserModule → 用户管理
InterviewModule → 面试功能
PaymentModule → 支付功能
ResumeModule → 简历管理
...

3. 全局组件(providers)

ResponseInterceptor → 统一响应格式
AllExceptionsFilter → 统一错误处理

关键理解

  • AppModule 是项目的”目录”,所有功能都在这里注册
  • 全局配置在这里,功能模块也在这里
  • 想知道项目有什么功能?看 AppModule 就够了

💡 类比:就像一本书的目录,告诉你每一章讲什么。


1.3 模块化的本质#

为什么要分模块?

想象你在整理房间:

  • 不分模块:所有东西堆在一起,找不到
  • 分模块:衣服放衣柜,书放书架,工具放工具箱

模块的四个部分

@Module({
imports: [...] // 我需要用到的其他模块
providers: [...] // 我提供的服务(Service)
controllers: [...] // 我提供的接口(Controller)
exports: [...] // 我允许别人用的服务
})

关键理解

  • 每个模块是一个独立的功能单元
  • 模块内部的东西,外部默认看不到
  • 想让别人用你的 Service?必须 exports

💡 类比:模块就像乐高积木,每块积木独立,但可以组合。


二、请求的旅程:从进入到返回 🚀#

2.1 完整的请求流程#

假设用户发起一个请求:POST /interview/mock/start

它会经历这些步骤

1. 请求进入应用(main.ts)
2. ValidationPipe 验证数据
- 检查参数格式是否正确
- 不符合?直接拒绝,返回 400 错误
3. JwtAuthGuard 验证身份
- 检查 Token 是否有效
- 无效?返回 401 错误(未认证)
4. RolesGuard 验证权限
- 检查用户是否有权限
- 没权限?返回 403 错误(禁止访问)
5. 到达 Controller
- 找到对应的方法
- 提取参数
6. Controller 调用 Service
- Service 处理业务逻辑
- 可能调用其他 Service
- 可能查询数据库
7. 返回数据
8. ResponseInterceptor 格式化响应
- 统一包装成 { code, message, data }
9. 返回给客户端

如果出错了呢?

任何一步出错
抛出异常
AllExceptionsFilter 捕获
格式化成统一的错误响应
返回给客户端

关键理解

  • 请求像流水线,每一步都有明确的职责
  • 验证、认证、授权都在 Controller 之前完成
  • 出错了不用担心,Filter 会统一处理

💡 类比:就像机场安检,先验票、再安检、再登机,每一步都有人负责。


2.2 各层的职责#

ValidationPipe:数据守门员

  • 职责:确保数据格式正确
  • 时机:最早,在所有逻辑之前
  • 例子:检查邮箱格式、密码长度

Guard:身份守门员

  • 职责:确认”你是谁”和”你能做什么”
  • 时机:在 Controller 之前
  • 例子:JWT 认证、角色权限

Controller:接待员

  • 职责:接收请求,调用 Service
  • 原则:只做”接待”,不做”业务”
  • 例子:提取参数,调用 Service

Service:业务专家

  • 职责:处理所有业务逻辑
  • 原则:不关心 HTTP,只关心业务
  • 例子:创建用户、生成面试题

Interceptor:包装员

  • 职责:统一处理请求和响应
  • 时机:在 Controller 前后
  • 例子:统一响应格式、记录日志

Filter:救火员

  • 职责:捕获所有错误
  • 时机:任何地方出错都会来这里
  • 例子:统一错误格式、记录错误日志

关键理解

  • 每一层只做自己的事,不越界
  • 这叫”关注点分离”(Separation of Concerns)
  • 好处:代码清晰、易于维护、易于测试

三、模块设计:从简单到复杂 📦#

3.1 简单模块:UserModule#

UserModule 做什么?

  • 管理用户信息
  • 提供用户查询和修改功能

它的结构

UserModule
├── User Schema → 用户数据结构
├── UserService → 用户业务逻辑
├── UserController → 用户接口
└── exports: [UserService] → 导出给其他模块用

为什么要导出 Service?

因为其他模块需要用户数据:

  • InterviewModule 需要检查用户配额
  • PaymentModule 需要更新用户余额
  • ResumeModule 需要关联用户简历

关键理解

  • 简单模块只负责一个功能领域
  • 通过 exports 让其他模块可以使用
  • 这是模块间协作的基础

3.2 复杂模块:InterviewModule#

InterviewModule 做什么?

  • 简历押题:用户上传简历,AI 生成问题
  • 模拟面试:AI 作为面试官,与用户对话

它为什么复杂?

因为它需要协调多个功能:

InterviewModule 需要:
├── 访问用户数据 → 导入 UserModule
├── 访问简历数据 → 导入 ResumeModule
├── 调用 AI 服务 → 使用 InterviewAIService
├── 解析文档 → 使用 DocumentParserService
├── 访问配置 → 导入 ConfigModule
└── 访问多个数据表 → 导入多个 Schema

三个 Service 的分工

InterviewService:指挥官

  • 协调整个流程
  • 调用其他 Service
  • 处理业务逻辑

InterviewAIService:AI 代理

  • 专门负责调用 AI API
  • 构造 Prompt
  • 处理 AI 响应

DocumentParserService:解析器

  • 专门负责解析文档
  • 提取文本内容
  • 支持多种格式

关键理解

  • 复杂模块需要多个 Service 协作
  • 每个 Service 只负责一个领域
  • 通过依赖注入组合在一起

💡 类比:就像一个公司,CEO(InterviewService)指挥,技术总监(AIService)负责技术,行政(ParserService)负责文档。


3.3 模块间的依赖关系#

整个项目的模块关系

AppModule(根模块)
├── UserModule(核心)
│ └── 导出 UserService
├── InterviewModule(中心)
│ ├── 导入 UserModule
│ ├── 导入 ResumeModule
│ └── 导入 ConfigModule
├── PaymentModule
│ └── 导入 UserModule
├── ResumeModule
│ └── 导入 UserModule
└── WechatModule
└── 导入 UserModule

关键理解

  • UserModule 是核心,几乎所有模块都依赖它
  • InterviewModule 是中心,它协调多个模块
  • 模块间通过 importsexports 建立依赖

四、核心概念深度理解 💡#

4.1 为什么要分模块?#

三个核心原因

1. 代码清晰

不分模块:
所有代码在一起 → 找不到 → 改不动
分模块:
用户相关在 UserModule → 一眼就找到

2. 易于维护

不分模块:
改一个功能,影响其他功能 → 容易出 bug
分模块:
改 UserModule,不影响 InterviewModule

3. 易于扩展

不分模块:
加新功能,不知道往哪加 → 代码越来越乱
分模块:
加新功能,创建新模块 → 结构清晰

4.2 为什么 InterviewModule 要访问多个 Schema?#

因为一个完整的业务流程涉及多个数据

用户发起面试:
1. 检查用户配额 → User Schema
2. 获取用户简历 → Resume Schema
3. 生成面试问题 → Interview Schema
4. 记录消费记录 → UserTransaction Schema
5. 保存面试结果 → InterviewResult Schema

关键理解

  • 真实项目的功能都是”跨数据表”的
  • 一个功能完整实现,需要多个数据支撑
  • 这在实际开发中非常常见

4.3 为什么要用全局拦截器和过滤器?#

问题:如果不用全局组件,会怎样?

❌ 每个 Controller 都要写:
@UseInterceptors(ResponseInterceptor)
@UseFilters(AllExceptionsFilter)
结果:
- 50 个接口,写 50 遍
- 改一次,改 50 个地方
- 容易漏掉,不统一

解决:用全局组件

✅ 在 main.ts 注册一次:
app.useGlobalInterceptors(new ResponseInterceptor())
app.useGlobalFilters(new AllExceptionsFilter())
结果:
- 写一次,处处生效
- 改一个地方,全部更新
- 不会漏,完全统一

关键理解

  • 全局组件遵循 DRY 原则(Don’t Repeat Yourself)
  • 减少重复代码,降低维护成本
  • 保证所有接口行为一致

4.4 为什么要用 Swagger?#

对比:手写文档 vs Swagger

手写文档(如 Postman)

❌ 改了接口,忘记更新文档
❌ 前端看的文档是旧的
❌ 前后端理解不一致
❌ 浪费时间对接

Swagger 自动文档

✅ 改了代码,文档自动更新
✅ 前端看的永远是最新的
✅ 前后端理解完全一致
✅ 可以直接在网页测试

关键理解

  • Swagger 让文档和代码同步
  • 降低前后端沟通成本
  • 提高开发效率

五、开发实战:如何添加新功能 🛠️#

5.1 开发流程#

假设你要开发一个新功能:“用户收藏面试题”

完整流程

1. 确定模块
→ 属于 InterviewModule
2. 设计数据结构(Schema)
→ 创建 FavoriteQuestion Schema
→ 字段:userId, questionId, createdAt
3. 定义接口参数(DTO)
→ CreateFavoriteDto: { questionId }
→ 返回值:{ id, questionId, createdAt }
4. 编写业务逻辑(Service)
→ FavoriteService.create()
→ 检查是否已收藏
→ 保存到数据库
5. 暴露接口(Controller)
→ POST /interview/favorite
→ @UseGuards(JwtAuthGuard)
6. 添加认证
→ 需要登录才能收藏
7. 编写文档(Swagger)
→ @ApiOperation({ summary: '收藏面试题' })
→ @ApiResponse({ status: 201, description: '收藏成功' })
8. 测试
→ 打开 Swagger UI
→ 测试接口

关键理解

  • 开发有固定的流程
  • 从数据到逻辑,从逻辑到接口
  • 每一步都有明确的目标

5.2 代码应该写在哪里?#

常见困惑

❓ 验证用户配额,写在哪里?
✅ 写在 Service 里(业务逻辑)
❓ 检查 Token,写在哪里?
✅ 用 Guard(认证逻辑)
❓ 统一响应格式,写在哪里?
✅ 用 Interceptor(横切关注点)
❓ 验证参数格式,写在哪里?
✅ 用 DTO + ValidationPipe(数据验证)
❓ 捕获错误,写在哪里?
✅ 用 Filter(错误处理)

关键理解

  • 每一层有明确的职责
  • 不要把业务逻辑写在 Controller
  • 不要把 HTTP 相关的东西写在 Service

六、核心收获总结 🎯#

6.1 项目组织#

main.ts → 应用入口,全局配置
AppModule → 根模块,组织所有功能
功能模块 → 按业务领域划分

6.2 请求流程#

请求进入
验证数据(Pipe)
验证身份(Guard)
处理请求(Controller → Service)
格式化响应(Interceptor)
返回客户端

6.3 模块协作#

通过 imports 导入其他模块
通过 exports 导出 Service
通过构造函数注入使用 Service

6.4 设计原则#

关注点分离 → 每一层只做自己的事
单一职责 → 每个类只有一个改变的理由
DRY 原则 → 不要重复代码
依赖注入 → 让框架管理依赖

结语 🎉#

恭喜你! 你已经建立了完整的 NestJS 知识体系。

现在的你:

  • ✅ 理解了项目的整体架构
  • ✅ 知道了各个部分如何协作
  • ✅ 掌握了开发的完整流程
  • ✅ 能够独立开发新功能

记住这三句话

  1. 模块化让代码清晰 - 相关的东西放在一起
  2. 分层让职责明确 - 每一层只做自己的事
  3. 全局组件减少重复 - 写一次,处处生效

下一步

  • 🚀 动手开发一个完整功能
  • 🚀 阅读优秀的开源项目
  • 🚀 在实践中不断优化

技术的学习没有终点,但你已经掌握了最重要的基础。 💪

继续加油!(。・ω・。)ノ♡

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

【NestJS】18 项目架构全景图:从零散知识到完整体系
https://blog.fridolph.top/posts/2026-02-01__nest18-fe-design/
作者
Fridolph
发布于
2026-02-01
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录