把学到的所有知识点串起来,建立完整的认知体系。不讲新东西,只帮你理清思路。
前言 ✨
学完了 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是中心,它协调多个模块- 模块间通过
imports和exports建立依赖
四、核心概念深度理解 💡
4.1 为什么要分模块?
三个核心原因:
1. 代码清晰
不分模块:
所有代码在一起 → 找不到 → 改不动
分模块:
用户相关在 UserModule → 一眼就找到2. 易于维护
不分模块:
改一个功能,影响其他功能 → 容易出 bug
分模块:
改 UserModule,不影响 InterviewModule3. 易于扩展
不分模块:
加新功能,不知道往哪加 → 代码越来越乱
分模块:
加新功能,创建新模块 → 结构清晰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
通过构造函数注入使用 Service6.4 设计原则
关注点分离 → 每一层只做自己的事
单一职责 → 每个类只有一个改变的理由
DRY 原则 → 不要重复代码
依赖注入 → 让框架管理依赖结语 🎉
恭喜你! 你已经建立了完整的 NestJS 知识体系。
现在的你:
- ✅ 理解了项目的整体架构
- ✅ 知道了各个部分如何协作
- ✅ 掌握了开发的完整流程
- ✅ 能够独立开发新功能
记住这三句话:
- 模块化让代码清晰 - 相关的东西放在一起
- 分层让职责明确 - 每一层只做自己的事
- 全局组件减少重复 - 写一次,处处生效
下一步:
- 🚀 动手开发一个完整功能
- 🚀 阅读优秀的开源项目
- 🚀 在实践中不断优化
技术的学习没有终点,但你已经掌握了最重要的基础。 💪
继续加油!(。・ω・。)ノ♡
- 本文链接:https://fridolph.top/posts/2026-02-01__nest18-fe-design
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。