--- 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 适合什么场景,不适合什么场景?