CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
NestJS 实战入门/课时/综合练习
RawBlame
课时目录12 节

NestJS 实战入门

  1. 01NestJS 是什么
  2. 02项目创建与结构
  3. 03Module 与依赖注入
  4. 04Controller 与路由
  5. 05Service 层
  6. 06Pipe 与校验
  7. 07Guard 与认证
  8. 08ConfigModule
  9. 09数据库接入入门
  10. 10异常与过滤器
  11. 11测试
  12. 12综合练习

综合练习

Tracks REST API;与 Spring Boot / Express 选型总结。

45 分钟

综合练习

这一课你将能做到

  1. 交付一个模块化的刷题平台 API:题库 CRUD、条件筛选、作答判分、成绩统计,校验、鉴权、异常、配置、测试齐备
  2. 用结业清单逐项自检,确认本轨道 12 节课的能力闭环
  3. 用自己的话写出 NestJS vs Express vs Spring Boot 的选型结论

为什么要学

前面每节课只教一个零件:Module、Controller、Service、Pipe、Guard、Config、异常、测试。本课要求你组装。

判断是否真学会一个框架,从来不是「看得懂示例」,而是能否从零搭出结构正确的项目,并处理好失败路径——非法输入 400、未登录 401、权限不足 403、资源不存在 404。本课做完,你应该能向同事演示一个接口契约稳定、测试可红可绿的完整后端。

刻意延续「做题平台」场景:同一套业务,本站 Java 侧用 Spring Boot 实现过,Node 侧你用 Nest 再实现一遍。对比之下你会真正理解——框架换的是骨架,业务本身从没变。

先建立图像

目标接口契约:

方法与路径访问规则成功状态
GET /api/health公开200
GET /api/questions公开,支持 difficulty/tag/page200
GET /api/questions/:id公开200 / 404
POST /api/questions管理员201 / 400 / 403
PATCH /api/questions/:id管理员200 / 404
DELETE /api/questions/:id管理员204 / 404
POST /api/submissions登录学生201 / 400 / 401
GET /api/submissions/me登录学生200
GET /api/submissions/me/stats登录学生200

选型对照表:

场景更合适的选择
脚本、极简原型、轻量 webhookExpress
TS 团队中型 API、BFF 聚合层NestJS
本站企业级主 API、复杂事务与生态Spring Boot
全栈页面加极简服务端逻辑Next.js Route Handler

跟着做

步骤 1:冻结需求与目录结构

沿用本轨道的 quiz-api,最终目录应近似:

src/
  main.ts
  app.module.ts
  config/
    jwt.config.ts
  common/
    filters/all-exceptions.filter.ts
    exceptions/business.exception.ts
  auth/
    auth.module.ts
    auth.controller.ts
    auth.service.ts
    guards/jwt-auth.guard.ts
    guards/roles.guard.ts
    decorators/  (public / roles / current-user)
  questions/
    questions.module.ts
    questions.controller.ts
    questions.service.ts
    dto/  (create / update)
  submissions/
    submissions.module.ts
    submissions.controller.ts
    submissions.service.ts
    dto/submit-answer.dto.ts
  health/health.controller.ts
test/
  app.e2e-spec.ts

按特征模块拆分,而不是一个文件堆一千行。

步骤 2:统一装配全局能力

建议新建 src/setup-app.ts,把全局装配抽成函数,main.ts 与 e2e 共用:

import {
  INestApplication,
  ValidationPipe,
} from "@nestjs/common";

export function setupApp(app: INestApplication): INestApplication {
  app.setGlobalPrefix("api");
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
    }),
  );
  app.enableShutdownHooks();
  return app;
}

main.ts 保持极简:

import { ConfigService } from "@nestjs/config";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { setupApp } from "./setup-app";

async function bootstrap() {
  const app = setupApp(await NestFactory.create(AppModule));
  const config = app.get(ConfigService);
  await app.listen(config.get<number>("PORT", 3000));
}
bootstrap();

全局过滤器通过 AppModule 的 APP_FILTER 注册,两个 Guard 通过 APP_GUARD 注册。

步骤 3:实现题库主路径

QuestionsService 使用内存数组(沿用 Service 课代码),保证:

  1. findOne 未命中 → NotFoundException(404)。
  2. create 中 answerIndex 越界 → BadRequestException(400)。
  3. 题干重复 → ConflictException(409)。
  4. 列表的 difficulty/tag 过滤与分页在 search 中一次完成。

健康检查独立成 HealthController 并贴 @Public():

import { Controller, Get } from "@nestjs/common";
import { Public } from "../auth/decorators/public.decorator";

@Controller("health")
export class HealthController {
  @Public()
  @Get()
  check() {
    return { ok: true, uptime: process.uptime() };
  }
}

步骤 4:完成作答与统计接口

SubmissionsController 挂全局 Guard 后默认需要登录,用 @CurrentUser() 取人:

import { Body, Controller, Get, Post } from "@nestjs/common";
import { CurrentUser } from "../auth/decorators/current-user.decorator";
import { SubmitAnswerDto } from "./dto/submit-answer.dto";
import { SubmissionsService } from "./submissions.service";

@Controller("submissions")
export class SubmissionsController {
  constructor(private readonly submissions: SubmissionsService) {}

  @Post()
  submit(@Body() dto: SubmitAnswerDto, @CurrentUser() user) {
    return this.submissions.submit(dto.questionId, user.id, dto.selectedIndex);
  }

  @Get("me/stats")
  myStats(@CurrentUser() user) {
    return this.submissions.stats(user.id);
  }

  @Get("me")
  mine(@CurrentUser() user) {
    return this.submissions.listByUser(user.id);
  }
}

注意路由顺序:me、me/stats 是静态段,没有 :id 冲突问题;将来若加详情路由,静态段必须写在参数段之前。

步骤 5:核对鉴权矩阵

逐一验证规则:公开接口贴 @Public();管理员接口 @Roles("admin");学生接口只要登录。用两个账号各打一遍:

# 管理员登录
curl -X POST http://localhost:3000/api/auth/login -H "Content-Type: application/json" -d "{\"username\":\"admin\",\"password\":\"admin123\"}"
# 学生登录
curl -X POST http://localhost:3000/api/auth/login -H "Content-Type: application/json" -d "{\"username\":\"student1\",\"password\":\"123456\"}"

预期:学生 Token 创建题目 → 403;学生 Token 提交作答 → 201;无 Token 提交 → 401。

步骤 6:补齐测试底线

最低要求,全部通过才允许宣布完成:

  1. QuestionsService 单测:命中、未命中 404、重复题干 409、answerIndex 越界 400。
  2. SubmissionsService 单测:判分正确、stats 正确率(mock QuestionsService)。
  3. e2e:GET /api/questions 200、GET /api/questions/999 404。
  4. e2e:未登录提交 401、学生建题 403、非法题体 400。

执行:

npm test
npm run test:e2e

步骤 7:写选型三段论(必须原创)

在笔记中各用 2~3 句回答,禁止只贴表格:

  1. 如果重做本站 Spring Boot 的题库模块,什么条件下你会用 Nest?
  2. 什么需求会让你退回 Express?
  3. 结合本站实际,核心交易与题库系统为什么仍交给 Spring Boot?

好的回答会提到团队语言、协作规模、生态与事务需求,而不是「谁火用谁」。

步骤 8:可选打磨

  1. npm install @nestjs/swagger,用 @ApiTags / @ApiProperty 生成 OpenAPI 文档页。
  2. 写 Dockerfile:先 npm run build,运行 node dist/main。
  3. 在路线图上勾选 NestJS 节点,回顾整条 TS 后端学习路径。

步骤 9:提交前对照检查表

  1. 九个端点的路径、方法、状态码与契约表一致。
  2. DTO 加全局 ValidationPipe 能拦非法输入,未知字段被拒绝。
  3. 404/401/403/409 都有自动化测试覆盖。
  4. ConfigModule 管理 PORT、JWT_SECRET,.env 未进 Git。
  5. 全局异常过滤器输出统一 JSON,500 不含堆栈。
  6. 选型三段论已用自己的话写进笔记。

缺哪项先按最短路径补齐,再做加分项。

讲清楚要点

  1. 综合练习考的是装配:全局 Pipe、Filter、前缀、Guard 必须真实注册,而不是停留在单课笔记里。
  2. 接口契约是产品:状态码与错误外壳要写到测试中,而不是只靠口头约定。
  3. 分层是贯穿主线:Controller 不写业务与 SQL,Service 不碰请求对象。
  4. 测试是交付门槛:最低单测加 e2e 通过,重构才有安全网。
  5. 选型服务于场景:语言、规模、生态决定框架,不存在「一个框架淘汰全部」。
  6. 本站语境要清晰:Spring Boot 仍是主栈,Nest 承担 TS 后端与 BFF 角色,二者长期共存。

你可能会懵的地方

  • 和 Express 版做同样的业务,算重复劳动吗? 刻意重复才有体感:同一业务两套骨架并排对比,DI 和 Module 带来的收益才看得见。

  • 结业必须接真实数据库吗? 不必。内存版可过关;分层正确比 ORM 花活更重要。将来按数据库课的替换点切换,Controller 一行不改。

  • 做到什么程度算入门完成? 初学者按硬指标判断:契约表全部打通 + 四类失败有处理 + 测试底线全绿 + 能解释选型。Swagger、Docker、Passport 都是加分项。

  • 时间紧怎么切? 先垂直打通一条:题库列表加单测;再补创建、校验、e2e;最后加鉴权。垂直切片优于横向铺开。

  • Next.js 的 Route Handler 和 Nest 冲突吗? 极简页面级 BFF 用 Route Handler 足够;接口一多、需要统一鉴权与模块边界时,独立 Nest 服务更清晰。

错法对照

错法问题正解
只实现 Happy Path失败行为全未定义补 400/401/403/404 契约与测试
所有类塞进 AppModule无法扩展与协作按业务域拆特征模块
没有测试就宣称完成回归全靠运气单测 + e2e 底线
写下「Nest 全面替代 Spring」脱离本站团队与生态按语言与场景选型
e2e 装配与 main.ts 不一致测试假绿抽 setupApp 公共函数

卡点急救

  1. 某个接口行为和契约表不符:回到该层排查——先看 Guard 规则,再看 Pipe 校验,最后看 Service 分支,逐层定位。
  2. 测试数据互相干扰:内存单例在用例间残留;beforeEach 重建模块或显式重置数组。
  3. 不确定是否可以结业:不要凭感觉,逐项过步骤 9 的检查表;全部打勾即完成,否则继续补缺。

面试常问

  • 完整介绍一下你用 NestJS 做的项目。 按「业务域 → 模块划分 → 请求链路(Guard/ Pipe/ Controller/ Service)→ 失败处理与统一异常 → 测试策略」组织,再补一句为什么选 Nest(团队 TS、规模适中),就是一份合格的项目陈述。

  • NestJS 相对 Express 多买到了什么? 强制模块边界、DI 容器、Guard/Interceptor/Pipe 标准扩展点、以及可测试的装配方式;代价是概念和样板更多。对小型脚本这些是负担,对多人中型项目是资产。

  • 同一个业务用 Spring 和 Nest 各做一遍,你体会到什么? 语言和注解不同,但分层思想同构:都是 HTTP 适配层转调业务层、横切逻辑前置、异常统一收口。框架能力的本质是约束与复用,业务模型与语言无关。

自测三问

  1. 你的全局 ValidationPipe 挂在哪里、用了哪三个选项?
  2. 列出三条被自动化测试覆盖的契约(含状态码)。
  3. 用自己的话说:Nest 相对 Express 多买到了什么、代价是什么?

小结一句话

Nest 轨道的闭环是:模块化 REST 加校验、鉴权、配置、异常与测试;框架服务场景,而不是信仰。

延伸

  • NestJS 官方文档总入口
  • Deployment(部署文档)
  • Recipes(Swagger、CQRS、GraphQL 等进阶)
  • 本课程到此结束。回顾 TS 后端成长路径:
javascript-core → typescript-essentials → nodejs-essentials → nestjs-essentials

下一步可选:Docker 容器化部署、CI/CD 流水线,或用 GraphQL / WebSocket 扩展刷题平台的实时能力。

加载本节练习…
← 上一课测试