Files
zhuangang/topics/backend/nodejs/01-express-nest-basics.md
T

93 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: backend.nodejs.express-nest
title: Node.js 后端(了解级):事件循环、Express 5 中间件与错误处理、NestJS 的模块化思路、做 BFF
type: lesson
domain: backend
stack: [nodejs]
tags: [Node.js, 事件循环, Express 5, 中间件, 错误处理, NestJS, 依赖注入, BFF, SSE]
level: basic
prerequisites: [frontend.typescript.type-system]
est_hours: 2.5
related: [frontend.ssr.nextjs-app-router, backend.nodejs.scenario.ai-bff, backend.nodejs.interview]
week: [7, 14]
order: 1
---
# Node.js 后端(了解级):事件循环、Express 5 中间件与错误处理、NestJS 的模块化思路、做 BFF
> 定位:**了解并能写**。你的主线后端是 Go(基础)+ Python(AI / Agent)。BOSS直聘 2026-10-23 的调研中有岗位提到 Node.js / Next.js,前端出身的候选人被问到 Node 的概率很高。示例基于 Node 24 + Express 5 实际运行通过;NestJS 部分是示意代码,未编译。以官方文档为准:https://nodejs.org/docs 、https://expressjs.com/ 、https://docs.nestjs.com/ 。
## 解释说明
### 事件循环(面试必问)
Node 单线程执行 JS,I/O 交给系统异步完成,回调放进队列。事件循环的主要阶段:timers(`setTimeout`)→ pending callbacks → poll(I/O)→ check(`setImmediate`)→ close callbacks;每个阶段之间先清空 `process.nextTick` 队列和 Promise 微任务队列。
**结论**:适合 I/O 密集(BFF、网关、实时推送);CPU 密集的计算会阻塞所有请求,要放到 `worker_threads` 或其他服务。
### Express 5
- **中间件**:`(req, res, next)` 按注册顺序执行,类似 Gin 的中间件链。
- **错误处理中间件**:4 个参数 `(err, req, res, next)`,放在最后。
- **Express 5 的变化**:async 处理函数抛出的错误 / 被拒绝的 Promise 会自动传给错误处理中间件(Express 4 需要手动 `next(err)`);路由路径语法也有调整(以迁移指南为准)。
### NestJS
在 Express(或 Fastify)之上提供 Angular 风格的结构:模块(Module)、控制器(Controller)、服务(Provider)、**依赖注入**、管道(Pipe,做校验)、守卫(Guard,做鉴权)、拦截器(Interceptor)。适合中大型团队统一规范。对照:Go 中你手写依赖注入,FastAPI 用 `Depends`。
### 为什么前端团队常用 Node 做 BFF
同一语言、类型可以共享;聚合多个后端接口、裁剪字段;统一鉴权、限流;流式转发 AI 回答。
## 案例
AI 客服的 BFF:校验登录、限流、为每个请求生成 request id,调用 Python AI 服务并把流式回答以 SSE 转发给浏览器;客户端断开时中止上游。
## 代码示例
[examples/express-bff](examples/express-bff/):`node --test app.test.ts` 4 个测试通过、`tsc` 通过,`node server.ts` 启动后 `curl -N` 观察到每 200ms 到达一条 SSE 事件。
```ts
// Express 5:async 路由中直接 throw,由统一错误处理中间件返回
app.get("/orders/:id", async (req, res) => {
if (!/^[A-Z]\d{3,}$/.test(req.params.id)) throw new HttpError(400, "BAD_ORDER_ID", "订单号格式不正确");
const order = await deps.findOrder(req.params.id);
if (!order) throw new HttpError(404, "ORDER_NOT_FOUND", "订单不存在");
res.json(order);
});
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
if (err instanceof HttpError) { res.status(err.status).json({ code: err.code, message: err.message }); return; }
res.status(500).json({ code: "INTERNAL", message: "服务器内部错误" }); // 不暴露内部细节
});
```
NestJS 的写法(示意,未编译;需要装饰器相关的编译配置):
```ts
@Controller("orders")
export class OrdersController {
constructor(private readonly orders: OrdersService) {} // 依赖注入
@Get(":id")
@UseGuards(AuthGuard) // 鉴权
find(@Param("id") id: string) {
return this.orders.findOne(id); // 抛出 NotFoundException 自动转成 404
}
}
```
## 易错点
- 在请求处理中做大量同步计算(大 JSON 序列化、加密、图片处理),阻塞所有请求。
- Express 4 的项目里 async 错误没有 `next(err)`,请求挂起直到超时。
- SSE 响应头发出后出错,还试图设置状态码。
- 没有监听客户端断开,上游 AI 继续生成、浪费 token。
- 未处理的 Promise 拒绝导致进程退出:要有全局监控和进程守护。
## 面试常问
- 讲讲 Node 的事件循环,`process.nextTick`、Promise、`setTimeout`、`setImmediate` 的顺序?
- Express 中间件原理?错误处理怎么做?
- Node 适合什么场景,不适合什么场景?