《NestJS 从入门到资深》书稿(Markdown)
作者:SOLO(与你协作完成) 版本:v0.1(目录 + 入门篇前 3 章正文)
说明:你选择的是"扩展版",完整成书会非常长(15--20 万字级别)。我先交付可直接写下去的整本书结构 ,并把入门篇最关键的前 3 章写成完整正文(含练习与检查清单)。接下来你只要告诉我"继续写第 X 章",我会按本书风格把该章补齐为完整正文。
0. 这本书适合谁?
本书面向三类读者(你也可以跳读对应路线):
- 完全新手 :Node.js / TypeScript 都不熟
- 建议:先看 附录 A(TypeScript 速成)+ 附录 B(Node.js/HTTP 速成),再从第 1 章开始。
- 会 Node/TS,不会 NestJS (最常见)
- 建议:从第 1 章开始,跟着示例项目一路做到上线。
- 已有 NestJS 经验,想进阶/资深
- 建议:入门篇快速扫读,重点看进阶篇(架构、性能、安全、测试)和资深篇(DDD、微服务、可观测性、部署、组织级工程实践)。
1. 你将做出什么?
1.1 旗舰示例项目(贯穿全书)
我们用一个更贴近企业真实场景的 SaaS 后台系统 贯穿全书:
项目名:AcmeHub(多租户项目协作平台)
- 多租户(tenant)隔离:按租户隔离数据、配置、权限
- 用户体系:注册/登录、JWT、刷新令牌、RBAC
- 核心业务:项目(Project)、任务(Task)、评论(Comment)、通知(Notification)
- 工程化:配置分层、日志、链路追踪、错误监控、测试金字塔
- 数据层:PostgreSQL(主),Redis(缓存/限流),可选 MQ(异步任务)
- 部署:Docker Compose → Kubernetes(可选)
1.2 补充示例(按章节"支线任务"出现)
为满足"多业务场景都有"的需求,本书在对应章节加入三组可独立运行的支线模块:
- 电商/订单:订单状态机、幂等、支付回调、库存扣减、延迟关单
- 内容/社区:帖子/评论/关注、反作弊、敏感词、审计日志
- 通用脚手架:企业后端模板(分层、规范、代码生成、CI)
2. 本书约定(重要)
2.1 代码约定
- TypeScript 严格模式(strict)
- DTO + 校验(class-validator)是默认写法
- 所有对外输入都要:校验、转换、白名单、错误可观测
- 统一异常格式(HTTP/业务错误码)
- 模块边界清晰:控制器(Controller)只做协议适配;业务在 Service;复杂业务抽 Domain/UseCase(进阶篇)
2.2 学习方式
每章结构固定为:
- 本章目标
- 核心知识
- 项目落地(AcmeHub)
- 常见坑
- 练习题
- 检查清单(Checklist)
3. 全书目录(入门 → 进阶 → 资深)
目录是扩展版深度;如果你希望精简,也可以告诉我删减哪些章节。
第一篇:入门(能写、能跑、能做出 API)
- NestJS 是什么:为什么是它?
- 第一个 NestJS 应用:项目结构与开发体验
- 依赖注入与模块系统:Controller / Provider / Module
- DTO 与数据校验:Pipe、ValidationPipe、class-validator
- 异常处理与统一返回:Exception Filter + 错误码体系
- 配置管理:ConfigModule、环境变量、配置分层
- 日志:从 console 到结构化日志(Pino/Winston)与请求追踪
- API 文档:Swagger(OpenAPI)与"可维护的接口契约"
- 数据库入门:ORM 选择与迁移(TypeORM/Prisma 选型与落地)
- CRUD 实战:用户与项目模块(AcmeHub v1)
第二篇:进阶(像"工程"一样写后端)
- 架构分层:Controller-Service-Repository 的边界
- 认证与授权(上):JWT、Refresh Token、注销与会话策略
- 认证与授权(下):RBAC、ABAC、资源级权限与审计
- 事务与一致性:数据库事务、领域事件、最终一致性入门
- 缓存:Redis、Cache-Aside、热点与雪崩、缓存键规范
- 幂等与并发控制:请求幂等、分布式锁、乐观锁/悲观锁
- 消息队列与异步任务:BullMQ/Redis、重试、死信与延迟任务
- 文件上传与对象存储:本地/MinIO/S3、断点续传思路
- WebSocket 与实时系统:通知、在线状态与房间管理
- 测试(上):单元测试(Jest)、依赖注入下的可测设计
- 测试(下):集成测试、e2e、Testcontainers 思路
- 性能(上):Node 事件循环、阻塞点、Profiling 基础
- 性能(下):分页/游标、N+1、批量、索引与慢查询治理
- 安全(上):输入校验、CORS、CSRF、XSS、SSRF、注入与反序列化
- 安全(下):限流、风控、密钥管理、依赖安全与供应链
- 可观测性(上):日志、指标、追踪(OpenTelemetry)
- 可观测性(下):告警、SLO、故障演练与线上排障套路
第三篇:资深(架构演进、系统设计与组织级实践)
- 领域建模与 DDD:从贫血模型到领域服务
- 模块化单体:边界清晰的"可演进单体"
- 微服务拆分策略:不要为了微服务而微服务
- 通信:REST vs gRPC vs MQ,契约管理与版本治理
- 数据拆分与一致性:分库分表、Saga、Outbox、CQRS 入门
- 多租户(进阶):隔离模型(库/Schema/行级)、迁移与运维
- 网关与 BFF:统一鉴权、聚合、灰度、限流与熔断
- 配置中心与特性开关:灰度发布、A/B、回滚策略
- 部署(上):Docker、Compose、环境一致性
- 部署(下):Kubernetes、Helm、滚动更新与资源治理
- CI/CD:流水线、质量门禁、自动化回归与发布规范
- 代码规范与工程化:Monorepo、模块边界、依赖规则、生成器
- 组织级实践:技术债、架构评审、事故复盘、带团队的后端方法论
附录
A. TypeScript 速成:你需要的那 20%(面向 NestJS) B. Node.js/HTTP 速成:请求生命周期与常用概念 C. SQL 必备:索引、事务隔离级别、锁与慢查询 D. 常用中间件:Nginx、Redis、PostgreSQL、MinIO 快速上手 E. 面试题与系统设计题:从初级到资深的题库与解法
第一篇:入门
第 1 章 NestJS 是什么:为什么是它?
本章目标
读完本章你将能回答:
- NestJS 解决了 Node 后端哪些"长期痛点"?
- NestJS 的核心设计哲学是什么?
- NestJS 适合哪些项目,不适合哪些项目?
1.1 Node 后端常见痛点
很多团队从 Express/Koa 起步很快,但随着业务增长会遇到:
- 缺少结构约束:路由、控制器、业务逻辑、数据访问混在一起
- 依赖管理混乱:手写 new、手动组装对象,难测、难替换、难重构
- 横切关注点散落:鉴权、日志、校验、异常格式、监控到处重复
- 大型项目协作困难:模块边界模糊,新人难上手
NestJS 的价值不是"让你写出一个 Hello World",而是让你在项目变大后仍然: 可维护、可测试、可协作、可演进。
1.2 NestJS 的核心:模块化 + 依赖注入 + 装饰器
NestJS 借鉴了后端成熟生态的经验(尤其是 Java/Spring 的工程化思想),在 Node/TS 世界给出一套相对完整的工程框架:
- 模块(Module):把业务能力封装成可组合的模块
- 依赖注入(DI):把"创建依赖"交给框架容器,你只描述"我需要什么"
- 装饰器(Decorator):用声明式方式描述路由、参数、校验、守卫等
- 统一的扩展点:Pipe / Guard / Interceptor / Filter,让横切能力标准化
1.3 什么时候选 NestJS?
适合:
- 中大型后端项目(多人协作、长期演进)
- 对测试、规范、可观测性有要求的团队
- 想要快速搭建"企业级后端骨架"的项目
不太适合(至少要谨慎):
- 极小型脚本或一次性项目(工程化成本可能偏高)
- 团队完全不接受强约束(例如讨厌装饰器/DI 的心智负担)
1.4 本书的"工程目标"
本书不仅教 API 怎么写,更要把你带到"资深后端的视角":
- 如何设计模块边界
- 如何让系统可测、可观测、可部署
- 如何应对一致性、并发、性能与安全
练习题
- 你当前团队/项目最痛的 3 个问题是什么?它们能否用"模块化 + DI + 统一扩展点"缓解?
- 你觉得"强约束"对团队是利大于弊还是弊大于利?为什么?
Checklist(本章检查清单)
- 我能用自己的话解释 NestJS 解决的痛点
- 我能说清楚 Module/DI/Decorator 三者的关系
- 我知道 NestJS 适用/不适用的场景
第 2 章 第一个 NestJS 应用:项目结构与开发体验
本章目标
- 跑起来一个 NestJS 项目
- 理解 Nest 项目的基本结构与请求流转
- 知道如何启用常用开发能力(热更新、环境变量、基础配置)
2.1 创建项目(两种方式)
方式 A:使用 Nest CLI(推荐)
- 优点:脚手架与生成器体验最好
- 缺点:需要安装 CLI
方式 B:自己搭建(适合已有工程体系)
- 优点:可控、可与 Monorepo/自定义构建整合
- 缺点:需要你对 TS/构建更熟
本书不强制你使用哪种方式。你只需要最终得到一个能
start:dev的 Nest 项目即可。
2.2 典型目录结构说明
你会经常看到类似结构:
text
src/
main.ts # 应用入口:创建 Nest 应用、注册全局中间件等
app.module.ts # 根模块:聚合所有业务模块
app.controller.ts # 示例控制器(通常会被你删除或改造)
app.service.ts # 示例服务
test/
app.e2e-spec.ts
2.3 请求在 Nest 中怎么走?
简化后的请求链路:
- 请求进入(可能先经过 Middlewares)
- 进入路由匹配到 Controller 的某个方法
- 在进入方法前,可能经过:
- Guard(鉴权/权限)
- Interceptor(拦截:日志、缓存、超时、响应映射)
- Pipe(参数校验与转换)
- Controller 调用 Service(业务逻辑)
- 发生异常 → Exception Filter 统一处理
- 返回响应
先记住一句话:
Controller 负责"协议适配",Service 负责"业务逻辑"。
2.4 main.ts:你最常改的文件之一
main.ts 通常做:
- 全局 ValidationPipe
- 全局异常过滤器
- 全局前缀(如
/api) - Swagger 初始化
- 启动端口与监听
下面是一个"书中推荐的 main.ts 形态"(先理解,不要求一次写全):
ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 移除 DTO 未声明字段
forbidNonWhitelisted: true, // DTO 外字段直接报错(更安全)
transform: true, // 自动把入参转成 DTO 类型
}));
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
whitelist/forbidNonWhitelisted/transform是"企业级默认值"。你会在第 4 章深入理解它们。
2.5 "AcmeHub"项目:我们从什么开始?
入门篇我们会先把 AcmeHub 做到:
- 能跑
- 有用户与项目两个模块
- 有基础的校验、异常格式、Swagger
- 能连上数据库并完成 CRUD
常见坑
- 忘了开启 transform:导致 DTO 类型不生效(例如数字字符串没转成 number)
- 没有 forbidNonWhitelisted:前端多传字段被悄悄忽略,安全与可观测性变差
- Controller 里写业务:短期快,长期重构成本巨大
练习题
- 解释
ValidationPipe三个关键选项:whitelist、forbidNonWhitelisted、transform各解决什么问题? - 画出请求从进入到返回的链路,标注 Guard/Pipe/Interceptor/Filter 在哪里生效。
Checklist
- 我知道 main.ts 的职责
- 我理解 Nest 的请求链路中各扩展点的位置
- 我能说清楚为什么 Controller 不该塞业务逻辑
第 3 章 依赖注入与模块系统:Controller / Provider / Module
本章目标
- 理解"依赖注入"是什么,为什么对可测试/可维护重要
- 能写出一个模块:Controller 调 Service,Service 作为 Provider 注入
- 理解 Provider 的作用域与导入/导出(exports)
3.1 先把话说清:什么是依赖注入(DI)?
不使用 DI 的写法(伪代码):
ts
class UserService {
private repo = new UserRepository(); // 自己 new
}
问题:
- UserService 依赖了 UserRepository 的"具体实现",替换困难
- 测试时想换成 FakeRepo/MockRepo,不好做
- 当依赖越来越多,构造与生命周期管理会变成灾难
使用 DI 的思想:
- 你只声明"我需要一个 IUserRepository"
- 容器负责"提供一个实现"
NestJS 通过 Provider + 注入容器来管理这些对象。
3.2 Controller / Provider / Module 之间的关系
三句话记住:
- Controller:对外提供 HTTP 接口
- Provider(通常是 Service):承载业务能力,可注入、可复用、可替换
- Module:把一组 Controller 与 Provider 组织在一起,并声明依赖关系
示例(简化):
ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
3.3 一个最小可用的"用户模块"
3.3.1 user.service.ts
ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
findById(id: string) {
return { id, name: 'demo' };
}
}
@Injectable() 的意义:
- 告诉 Nest:这是一个可被容器管理的 Provider
- 容器可以创建它并注入它的依赖(如果它构造函数里声明了依赖)
3.3.2 user.controller.ts
ts
import { Controller, Get, Param } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
getUser(@Param('id') id: string) {
return this.userService.findById(id);
}
}
这里发生了什么?
- Controller 的构造函数声明了依赖
UserService - Nest 容器会在运行时把
UserService实例注入进来
3.3.3 user.module.ts
ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
然后在 app.module.ts 引入即可:
ts
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
@Module({
imports: [UserModule],
})
export class AppModule {}
3.4 Provider 的导入与导出:exports
当你希望 别的模块也能注入你的 Provider 时,需要:
- 在提供者所在模块
providers里声明它 - 并在
exports导出它
ts
@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
然后其他模块 import UserModule,就可以注入 UserService。
经验:能不导出就不导出。导出意味着"对外承诺",会增加耦合。进阶篇我们会讲如何设计模块边界。
3.5 常见坑与排查
- Nest 无法解析依赖(Cannot resolve dependencies)
多半是:没在 providers 声明、没在 exports 导出、没在 imports 引入。 - 循环依赖(Circular dependency)
初学时最常见。解决方式包括:重新划分模块边界、抽取公共模块、forwardRef(但不要滥用)。 - 把"配置/常量"当类注入
需要使用自定义 token(进阶篇会系统讲)。
练习题
- 用自己的话解释:为什么"自己 new 依赖"会让测试更难写?
- 写一个
ProjectModule,包含ProjectController和ProjectService,实现GET /projects/:id。 - 让
ProjectService依赖UserService,并解释你需要做哪些 imports/exports 才能注入成功。
Checklist
- 我知道 Controller/Provider/Module 的职责边界
- 我能写出一个可注入的 Service 并在 Controller 中使用
- 我理解 exports 的意义,以及它为什么会带来耦合
接下来写什么?
你可以直接对我说:
- "继续写第 4 章(DTO 与数据校验)"
- 或者"先把进阶篇第 12 章(JWT 与刷新令牌)写出来"
我会按本书风格补齐为完整正文,并持续更新这份 Markdown 书稿文件。