phase 5 part 3: mongodb, elasticsearch hybrid search, data-pipeline, nodejs BFF topics with verified examples

This commit is contained in:
Shikui Wang committed 2026-10-23 16:38:52 +08:00
1 parent 9eeb10aede
commit 075872e31e
27 files changed
+2405 -52

No files matched your search

@@ -0,0 +1,92 @@
---
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 适合什么场景,不适合什么场景?