场景速查
从你要做的事出发:每一行给出简要回答,并带你找到讲解和 TaskFlow 代码。要查某个具体关键词(装饰器、错误信息),也可以 用页面顶部的搜索。
创建项目与理解运行机制
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要像计划里那样创建一个用 Jest 的 Nest 项目 | 在终端里运行 nest new,选 CommonJS + Jest(默认是 ESM + Vitest,没有交互式终端时也只会得到它) | J1 · 生成项目、CLI 的提问 |
| 想弄明白为什么一个慢请求会卡住其他所有请求 | 只有一个 JavaScript 线程:任何同步计算都会阻塞事件循环 | 事件循环与 thread-per-request |
| 要执行繁重的 CPU 计算,又不阻塞 API | worker_threads 或任务队列 | CPU 计算 |
| 要并行发起多个 I/O 调用 | Promise.all,不要在循环里逐个 await | Promise 与 async/await |
| 要在本机启动 PostgreSQL 和 Adminer | docker compose up -d | Docker Compose |
| 要添加一个模块、一个控制器或一整套资源 | nest g module、nest g controller、nest g service、nest g resource | Nest CLI、schematic |
| 要生成完整的 CRUD,又不想回答提问 | nest g resource orders --type rest --crud | CRUD 资源 |
| 要查某个 schematic 的别名(guard、interceptor 等) | gu、itc、pi、f、mi……;nest g --help | Schematic 与别名 |
| 要在执行命令之前看看它会创建什么 | -d(--dry-run) | 生成选项 |
| 想弄明白为什么生成的 guard 没有生效 | guard、管道、过滤器、拦截器和中间件不会被自动声明:要自己接上 | Schematic 与别名 |
| 要在多个应用之间共享代码 | nest g app、nest g lib(项目会变成 monorepo) | Monorepo |
| 要把 NestJS 升级一个大版本 | 先 nest info,再 nest upgrade -d | 添加与升级 |
| 要弄清项目里什么东西放在哪 | 每个领域一个模块,每个模块有自己的 dto/ 和 entities/ | 仓库结构 |
路由、请求与响应
| 需求 | 简要回答 | 详见 |
|---|---|---|
DELETE 要返回 204,POST 动作要返回 200 | @HttpCode(HttpStatus.NO_CONTENT) / @HttpCode(HttpStatus.OK) | 状态码 |
要给所有路由加上 /v1 前缀 | app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' }) | API 版本 |
要让 /health 不参与版本 | @Controller({ path: 'health', version: VERSION_NEUTRAL }) | 健康检查 |
| 要读取 UUID 路径参数,其他值一律拒绝 | @Param('id', ParseUUIDPipe) → 不合法时返回 400 | 管道 |
| 要按查询参数里的枚举值过滤 | @Query('status', new ParseEnumPipe(TaskStatus, { optional: true })) | 管道 |
| 要给列表分页 | PaginationQueryDto(page、limit)+ findAndCount / getManyAndCount | 分页、搜索 |
| 要在处理函数里拿到当前登录用户 | @CurrentUser() user: AuthUser | @CurrentUser() |
| 要设置响应头,又不丢掉序列化 | @Header() 或拦截器,不要用 @Res() | 在控制器里用 @Res() |
输入的校验与转换
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要校验 JSON 请求体 | DTO 类 + class-validator 装饰器 + 全局 ValidationPipe | DTO 与校验 |
| 要拒绝未知字段 | whitelist: true, forbidNonWhitelisted: true | 全局 ValidationPipe |
| 要写更新用的 DTO,又不想重复字段 | PartialType(CreateXDto)、PickType、OmitType | Mapped types |
| 要从查询字符串接收数字或布尔值 | @Type(() => Number),布尔值用 @Transform,再加 transform: true | 分页与过滤 |
| 想弄明白为什么 TypeScript 类型什么都挡不住 | 类型在运行时被擦除了 | 运行时校验 |
| 要自己写一个转换管道 | PipeTransform | 管道 |
依赖注入与模块
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要注入一个接口 | 一个 token(Symbol)+ @Inject(TOKEN) + useClass / useFactory provider | Token、ProjectsStore |
| 要根据配置选择实现 | 带 inject: [ConfigService, …] 的 useFactory provider | ProjectsStore |
| 要使用另一个模块的 service | 源模块把它加进 exports,你的模块导入源模块 | 模块 |
| 要解决“Nest can't resolve dependencies of …” | provider 没声明、没导出,或参数类型被擦除了 | 依赖解析、design:type |
| 要用另一个 provider 来配置某个模块 | forRootAsync / registerAsync,配合 inject + useFactory | 动态模块 |
| 要注入一个不是类的值(时钟、常量) | { provide: CLOCK, useValue: … } + @Inject(CLOCK) | CommonModule |
| 要在某个变量存在时才加载模块 | ConditionalModule.registerWhen(Module, (env) => …),传入一个函数 | ConditionalModule |
| 要打破循环依赖 | 重新划分模块;forwardRef() 是最后手段 | forwardRef |
| 要为每个请求保存各自的状态 | 绝不要放在单例里;可以用 REQUEST scope,但要清楚它的代价 | Scope、单例里的状态 |
数据库与 TypeORM
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要接入 PostgreSQL | TypeOrmModule.forRootAsync + 每个模块各自 forFeature([Entity]) | @nestjs/typeorm |
要声明一个带 UUID 和 timestamptz 日期的实体 | @PrimaryGeneratedColumn('uuid')、@CreateDateColumn({ type: 'timestamptz' }) | Project 实体 |
要替代 findByNameContaining | find({ where: { name: ILike('%…%') } }) 或 QueryBuilder | 搜索 |
| 要写带 join 的查询 | createQueryBuilder().innerJoin(…) | QueryBuilder |
| 要删除数据但保留这一行 | @DeleteDateColumn + softDelete() | Repository |
| 要关联两个实体 | @ManyToOne + @JoinColumn,外键列显式声明 | 关系、Task |
| 要避免 N+1 | relations 或 leftJoinAndSelect | N+1 |
| 要让多次写入全部成功或全部不生效 | dataSource.transaction(async (manager) => …),所有操作都走 manager | 事务、归档项目 |
| 要精细控制事务 | QueryRunner | QueryRunner |
| 要修改数据库 schema | migration:generate → 检查 → migration:run;生产环境绝不用 synchronize | 迁移、synchronize |
| 要在 Docker 镜像里执行迁移 | npm run migration:run:prod,和启动分开,单独一步 | Docker |
| 要给开发库填充数据 | npm run seed | 种子数据 |
错误、日志与请求管道
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 想知道中间件、guard、管道等按什么顺序执行 | Middleware → Guards → Interceptors → Pipes → Handler → Interceptors → Filters | 执行顺序 |
| 要统一所有错误响应的格式 | 一个全局 ExceptionFilter(APP_FILTER) | AllExceptionsFilter |
| 数据库里出现重复数据时要返回 409 | 在过滤器里把 PostgreSQL 错误码 23505 转成 CONFLICT | TypeORM 错误 |
| 资源不存在时要返回 404 | throw new NotFoundException() | HTTP 异常 |
| 要测量每个请求的耗时 | 一个用 RxJS tap 的 NestInterceptor | LoggingInterceptor |
| 要在所有日志里追踪同一个请求 | x-correlation-id 请求头 + nestjs-pino 的 genReqId | 追踪 ID、JSON 日志 |
| 绝不能暴露调用栈或 SQL | 过滤器返回通用的 500,把细节写进日志 | 全局过滤器 |
要找出漏写的 await | no-floating-promises 规则(Nest 12 starter 自带的 oxlint) | 漏写 await |
配置与生命周期
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要每个环境一个配置文件 | NODE_ENV + .env.development、.env.test;只有 .env.example 纳入版本控制 | 多环境配置、.env 文件 |
| 配置不合法时要拒绝启动 | ConfigModule.forRoot({ validate }) | 环境变量校验 |
| 要注入类型化的配置 | registerAs('database', …) + @Inject(databaseConfig.KEY) | 类型化配置 |
想弄明白为什么 PORT 还是字符串 | 写明类型:PORT: number = 3000 | 类型元数据 |
| 要在启动或关闭时执行代码 | OnModuleInit、OnApplicationShutdown + enableShutdownHooks() | 生命周期 |
| 生产环境要输出 JSON 日志 | nestjs-pino,pino-pretty 只在开发环境用 | 日志 |
安全
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要默认保护所有路由 | 全局 JwtAuthGuard(APP_GUARD) | 全局 guard |
| 要开放一个不需要 token 的路由 | @Public(),由 guard 通过 Reflector 读取 | 公开路由 |
| 要让某个路由只对管理员开放 | @Roles(Role.Admin) + RolesGuard → 403 | RBAC |
| 要存储密码 | argon2id(argon2.hash、argon2.verify) | 密码哈希 |
不能返回 passwordHash | @Exclude() + ClassSerializerInterceptor(再加 @ApiHideProperty(),让它不出现在文档里) | 直接返回实体 |
| 要让用户只能看到自己的项目 | 按成员关系过滤;对其他项目返回 404(而不是 403) | 用户只能看到自己的项目 |
| 要限制登录尝试次数 | @Throttle({ default: { limit: 5, ttl: 60_000 } }) → 429 | 安全 |
| 要添加安全响应头并配置 CORS | helmet()、enableCors({ origin }) | 安全 |
| 要延长会话,又不让用户重新输入密码 | Refresh token(TaskFlow 没有实现) | Refresh token |
事件、定时任务、队列
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要响应某个变化,又不让 service 互相耦合 | EventEmitter2.emit() + @OnEvent() | task.status.changed 事件 |
| 不想让监听器里的错误丢失 | 在监听器里 try/catch 并写日志 | 异步错误 |
| 要每天夜里执行一次处理 | @Cron(CronExpression.EVERY_DAY_AT_1AM, { timeZone }) | OVERDUE 定时任务 |
| 要在正确的时区里计算“今天” | 用巴黎的日历日期,而不是 toISOString()(UTC) | OVERDUE 定时任务、OVERDUE |
| 处理失败时要延迟重试 | BullMQ:attempts + 指数 backoff | BullMQ 队列 |
| 要让 BullMQ 6 跑起来 | 显式安装 ioredis | BullMQ 缺少 ioredis |
测试
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要在模拟依赖的情况下测试 service | Test.createTestingModule + { provide: X, useValue: mock } | Testing module |
| 要模拟 TypeORM repository | { provide: getRepositoryToken(Task), useValue: … } | 模拟 repository |
| 要对 API 做端到端测试 | AppModule + supertest + 真实的 PostgreSQL 数据库 | 端到端流程 |
| 要隔离测试数据库 | db-test 服务(tmpfs)或 Testcontainers | 测试数据库 |
| 覆盖率低于阈值时要让 CI 失败 | jest.config.ts 里的 coverageThreshold | 覆盖率阈值 |
| 要解决测试里的“Reflect.getMetadata is not a function” | import 'reflect-metadata' | reflect-metadata |
| 要让“今天”这个日期可以测试 | 注入一个时钟(CLOCK) | 单元测试 |
文档与上线
| 需求 | 简要回答 | 详见 |
|---|---|---|
| 要生成 API 文档,又不想给每个字段加注解 | nest-cli.json 里配置 @nestjs/swagger CLI 插件,文件命名为 *.dto.ts | Swagger |
| 要提供 liveness 和 readiness 探针 | Terminus:/health/live 不依赖任何外部服务,/health/ready 检查数据库 | 健康检查 |
| 要构建一个轻量、非 root 的镜像 | 多阶段 Dockerfile、npm ci --omit=dev、USER node | Dockerfile |
| 要在本机运行容器化的 API | 执行完迁移后,启动 Compose 的 app profile | Compose profile |
| 要在 CI 里检查每个分支 | lint-and-build job、单元测试、带 PostgreSQL 服务的端到端测试 | GitLab 流水线 |
| 要把整个课程好好收尾 | 回顾 LEARNINGS.md,补全 README | 最终回顾 |