尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Node.js + Express 从零搭建后端 API 服务实战:路由、中间件与错误处理

Node.js + Express 从零搭建后端 API 服务实战:路由、中间件与错误处理 1. 为什么我选这个项目来练手1.1 一个后端接口服务到底能解决什么问题很多人学 Node.js 和 Express 的时候卡在一个很尴尬的位置语法看懂了教程跟着敲完了但真让自己从零写一个能跑、能对外提供服务、能被别人调用的东西就不知道从哪下手。我当初也是这样看了一堆文档app.get会写req.body会取但一个完整的 API 服务应该长什么样心里没谱。这个项目的定位就是填这个坑。它做的事情很具体用 Node.js 加 Express 搭一个 HTTP API 服务对外暴露几个接口能接收请求、处理数据、返回 JSON 结果。听起来简单但麻雀虽小五脏俱全路由设计、中间件、错误处理、参数校验、日志、环境变量、启动脚本这些真实项目里绕不开的东西它全都能覆盖到。适合谁来参考三类人。第一类是刚学完 JavaScript 基础想找个能落地的项目把知识串起来的新手第二类是前端开发者平时调别人的接口调得多想搞清楚后端那一侧到底在干什么第三类是想快速搭个原型服务验证想法的人比如你有个小工具需要个后端接口不想上重型框架Express 就是最省事的选择。我特别想强调一点这个项目里我会把 AI 用起来但不是让 AI 替你写完了事。AI 在这个流程里的角色更像一个随时在线的结对伙伴帮你生成样板代码、解释报错、补全你没想周全的边界情况。真正的设计决策、参数取舍、调试判断还是得你自己来。这样练下来你收获的不只是一份能跑的代码而是一套可复用的搭建思路。1.2 技术选型背后的取舍逻辑为什么是 Node.js 加 Express而不是别的组合这个问题值得说清楚因为选型逻辑本身就是经验的一部分。Node.js 的核心优势是它的事件驱动和非阻塞 I/O 模型。翻译成人话就是它处理大量并发请求的时候很省资源。传统的一个请求开一个线程的模型请求一多线程就爆炸而 Node.js 用单线程加事件循环把等待 I/O 的时间利用起来去处理别的请求。对于一个 API 服务来说大部分时间其实都花在等数据库、等外部接口上这种场景下 Node.js 特别合适。Express 则是 Node.js 生态里最成熟、最轻量的 Web 框架。它的哲学是不替你做决定只给你路由、中间件这些最核心的骨架剩下的你自己拼。对比一下 FastifyFastify 性能更好、内置了 schema 校验但学习曲线稍陡生态也没 Express 那么庞大。对于练手项目Express 的文档多、示例多、遇到问题好搜这个优势在初学阶段比那点性能差距重要得多。JavaScript 作为语言就不用多说了前后端统一你不用在两种语言之间来回切换思维。而且现在 Node.js 对 ES Module 的支持已经很完善写起来和前端体验一致。提示选型没有绝对的对错只有适不适合当前场景。练手阶段优先选生态成熟、资料多的方案能让你把精力集中在理解原理上而不是跟冷门工具的坑较劲。2. 动手前的环境准备与项目骨架2.1 Node.js 安装与版本选择第一步是把 Node.js 装好。这里有个很多人会踩的坑版本选择。Node.js 有 LTS长期支持和 Current最新特性两条线。生产环境和学习项目我都建议用 LTS 版本因为它稳定、bug 少、社区支持周期长。Current 版本虽然新特性多但可能踩到还没修的问题。安装方式看你系统。Windows 和 macOS 直接去官网下载安装包一路下一步就行。Linux 用户我更推荐用版本管理工具比如 nvm这样你可以在多个 Node.js 版本之间切换不同项目用不同版本互不干扰。装完之后验证一下node -v npm -v两条命令分别输出 Node.js 和 npm 的版本号就说明装好了。npm 是 Node.js 自带的包管理器装 Node.js 的时候会一起装上不用单独装。注意如果你在安装过程中遇到类似某个版本尚未发布或不可用的报错八成是版本号写错了或者镜像源没同步。先确认版本号拼写再检查 npm 的 registry 配置。2.2 初始化项目与依赖安装找个空目录执行初始化mkdir my-api-service cd my-api-service npm init -ynpm init -y会生成一个默认的package.json这是项目的身份证记录了项目名、版本、依赖、脚本等信息。-y表示全部用默认值省得它一条条问你。接着装 Expressnpm install express如果你打算用 ES Module 的import语法而不是 CommonJS 的require需要在package.json里加一行type: module。我个人的习惯是新项目一律用 ESM语法更现代和前端保持一致。再装一个开发时用的工具 nodemon它能在你改代码后自动重启服务省得每次手动 CtrlC 再重跑npm install --save-dev nodemon--save-dev表示这是开发依赖只在开发环境用生产环境不需要。2.3 目录结构怎么规划才不乱新手最容易犯的错就是把所有代码堆在一个index.js里写到几百行之后自己都找不到东西。我建议一开始就分好目录哪怕项目小习惯养成了后面受益无穷。我常用的结构是这样的my-api-service/ ├── src/ │ ├── routes/ 路由定义 │ ├── controllers/ 业务逻辑 │ ├── middlewares/ 中间件 │ ├── utils/ 工具函数 │ └── app.js 应用入口 ├── .env 环境变量 ├── .gitignore └── package.json路由层只负责哪个 URL 对应哪个处理函数控制器层负责具体怎么处理中间件层放通用的拦截逻辑比如日志、鉴权。这样分层的好处是职责清晰改一处不影响另一处。项目小的时候可能觉得多此一举但等你加到第十个接口的时候就会庆幸当初分了层。3. 核心代码逐层拆解3.1 应用入口与中间件装配先看入口文件src/app.js的骨架import express from express; import routes from ./routes/index.js; const app express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(/api, routes); app.use((err, req, res, next) { console.error(err.stack); res.status(err.status || 500).json({ code: err.status || 500, message: err.message || 服务器内部错误 }); }); const PORT process.env.PORT || 3000; app.listen(PORT, () { console.log(服务已启动监听端口 ${PORT}); });这里有几个关键点值得展开。express.json()这个中间件负责解析请求体里的 JSON 数据解析完挂到req.body上。没有它你收到的req.body就是 undefined。express.urlencoded则是处理表单提交的数据。这两个几乎是标配除非你的服务完全不接收请求体。中间件的执行顺序很重要它是从上到下依次执行的。所以解析请求体的中间件必须放在路由之前否则路由里拿不到req.body。错误处理中间件必须放在所有路由之后而且它比其他中间件多一个参数errExpress 靠参数个数来识别它是不是错误处理中间件。实操心得错误处理中间件一定要写而且要放在最后。我见过太多新手项目接口一出错就直接把整个堆栈信息返回给前端既不安全也不友好。统一在错误处理中间件里格式化返回是专业和业余的分水岭。3.2 路由设计与 RESTful 风格路由是 API 的门面设计得好不好直接影响调用方的体验。我遵循的是 RESTful 风格的基本原则用 HTTP 方法表达操作意图用 URL 表达资源。import { Router } from express; import * as userController from ../controllers/userController.js; const router Router(); router.get(/users, userController.listUsers); router.get(/users/:id, userController.getUser); router.post(/users, userController.createUser); router.put(/users/:id, userController.updateUser); router.delete(/users/:id, userController.deleteUser); export default router;GET 用来查POST 用来建PUT 用来改DELETE 用来删。URL 里用名词复数表示资源集合/users/:id里的:id是路径参数表示具体某一个资源。为什么强调 RESTful因为它是一套约定俗成的规范调用方看到你的接口就能猜到怎么用不用翻文档。比如看到GET /users就知道是查用户列表看到DELETE /users/5就知道是删 id 为 5 的用户。这种一致性在团队协作里价值巨大。3.3 控制器里的参数校验与响应规范控制器是真正干活的地方。以创建用户为例export async function createUser(req, res, next) { try { const { name, email } req.body; if (!name || typeof name ! string) { return res.status(400).json({ code: 400, message: name 字段必填且必须是字符串 }); } if (!email || !/^[^\s][^\s]\.[^\s]$/.test(email)) { return res.status(400).json({ code: 400, message: email 格式不正确 }); } const newUser { id: Date.now(), name, email }; res.status(201).json({ code: 201, message: 创建成功, data: newUser }); } catch (err) { next(err); } }这段代码里有几个我坚持的习惯。第一参数校验必须做而且要在业务逻辑之前做。永远不要相信前端传来的数据哪怕是你自己写的前端。校验不通过就返回 400明确告诉调用方哪里错了。第二响应格式要统一。我习惯用{ code, message, data }这个结构code 是业务状态码message 是给人看的提示data 是实际数据。这样前端处理起来有规律可循不用每个接口单独适配。第三异步操作要用 try/catch 包起来出错就next(err)交给错误处理中间件。不这么做的话异步里的异常 Express 捕获不到服务可能直接崩。注意typeof判断数据类型的时候要小心typeof null返回的是objecttypeof []也返回object。要精确判断数组用Array.isArray()判断 null 直接 null。这个坑我踩过不止一次。3.4 用 AI 辅助开发的正确姿势这个项目里 AI 能帮上大忙但用法有讲究。我的经验是把它当成一个知道很多但需要你把关的助手。写样板代码的时候AI 特别高效。比如你要写五个 CRUD 接口把第一个写清楚让 AI 照着模式生成剩下四个几秒钟的事。解释报错也好用把错误信息贴给它它通常能指出问题所在还能给出修复建议。但有几件事不能全交给 AI。架构设计得你自己想因为 AI 不了解你的具体业务和约束。参数校验的边界条件得你自己定AI 给的校验往往不够严谨。还有安全性相关的判断比如哪些字段不能返回给前端这个必须你自己把关。我常用的提问方式是给足上下文。不要只说帮我写个接口而是说我在用 Express 写一个用户注册接口接收 name 和 email需要校验 email 格式返回统一的 JSON 结构请给出控制器代码。上下文越具体AI 给的代码越能用。实操心得AI 生成的代码一定要自己跑一遍、读一遍。我遇到过 AI 生成的代码用了不存在的 API或者逻辑上有个隐蔽的边界 bug。把它当草稿你当审稿人这个配合效率最高。4. 让服务更健壮的关键细节4.1 环境变量与配置分离把端口号、数据库连接串这些配置硬编码在代码里是大忌。一旦要换环境你得改代码重新部署。正确做法是用环境变量。装个 dotenvnpm install dotenv在入口文件最顶部加载import dotenv/config;然后建一个.env文件PORT3000 NODE_ENVdevelopment代码里用process.env.PORT读取。.env文件要加到.gitignore里绝对不能提交到代码仓库因为里面可能有敏感信息。团队协作时提供一个.env.example作为模板列出需要哪些变量但不填真实值。4.2 日志记录的分寸感日志不是越多越好也不是越少越好。开发阶段我习惯用console.log快速看数据但上线前一定要换成正经的日志方案比如 winston 或 pino。原因很简单console.log是同步阻塞的高并发下会拖慢服务而且它没法分级、没法输出到文件、没法结构化。日志要记录什么请求进来的方法、路径、耗时出错的堆栈关键业务节点的状态。不要记录什么用户的密码、token、完整的身份证号这类敏感信息。我见过有人调试时把整个req.body打出来结果密码明文进了日志文件这是安全事故。4.3 优雅关闭与进程管理服务不能想停就停。正在处理的请求如果被强行中断用户那边就是报错。优雅关闭的意思是收到停止信号后先停止接收新请求等正在处理的请求都完成了再退出进程。const server app.listen(PORT); process.on(SIGTERM, () { console.log(收到停止信号准备优雅关闭); server.close(() { console.log(所有连接已关闭进程退出); process.exit(0); }); });生产环境还会用 pm2 这类进程管理工具它能在服务崩溃时自动重启还能做负载均衡。练手阶段可以先了解概念等真部署的时候再上手。5. 常见问题排查速查5.1 接口调不通的排查顺序接口不通是最常见的问题我总结了一套排查顺序从外到内一层层剥。先确认服务起来了没看控制台有没有打印启动日志。再看端口对不对curl http://localhost:3000/api/users直接测一下。如果服务起了但请求 404检查路由注册的路径前缀对不对app.use(/api, routes)意味着路由里写/users实际访问是/api/users。如果返回 500看错误日志里的堆栈定位到具体哪一行。现象可能原因排查方法连接被拒绝服务没启动或端口不对检查启动日志和 PORT 配置404路由路径不匹配核对 app.use 前缀和路由定义400参数校验失败检查请求体格式和字段500代码抛异常看错误堆栈定位行号请求挂起无响应忘了 res.send 或异步没 await检查处理函数是否都有响应返回5.2 请求体拿不到的几种情况req.body是 undefined这个问题新手遇到最多。原因通常有三个没加express.json()中间件中间件加在了路由之后请求的 Content-Type 不是application/json。第三个最隐蔽。你用 Postman 测试的时候如果 body 选的是 form-data 而不是 raw JSONContent-Type 就不对express.json()不会解析它。这时候要么改请求格式要么加express.urlencoded()处理表单数据。5.3 异步错误导致服务崩溃在 Express 4 里路由处理函数如果是 async 的里面抛出的异常 Express 捕获不到会导致未处理的 Promise rejection。解决办法就是前面说的用 try/catch 包起来手动next(err)。或者用一个包装函数把 async 处理函数包一层自动捕获异常转发给错误中间件。Express 5 据说会原生支持 async 错误捕获但升级需谨慎生态兼容性要测。提示判断一个 Express 项目写得规不规范看它的错误处理就知道了。所有异步处理函数都有异常捕获所有错误都走统一的错误中间件这是基本要求。6. 这个项目还能怎么往下长搭完这个基础服务其实只是起点。往上加东西的路子很多我列几个方向供参考。加数据库是最自然的下一步。现在数据存在内存里一重启就没了。接个 SQLite 练手最合适零配置一个文件就是一个库。熟悉了再上 PostgreSQL 或 MySQL。数据访问层可以手写 SQL也可以用 ORM 比如 Prisma 或 Sequelize各有取舍手写 SQL 可控性强ORM 开发效率高。加鉴权是另一个方向。用户登录后发个 token后续请求带着 token 验证身份。这块涉及密码哈希、token 签发验证是后端安全的入门必修课。再往后可以加接口文档用 Swagger 自动生成调用方一看就懂。加单元测试用 Jest 或 Vitest 保证改动不破坏已有功能。加 Docker让部署变成一条命令的事。我个人在实际操作中的体会是小项目最大的价值不在于它本身多完整而在于它给了你一个可以不断往上叠加的基座。每加一个功能你就多理解一块后端开发的拼图。等这些拼图拼得差不多了你回头看已经能独立扛起一个像样的服务了。别急着一步到位先把眼前这个跑通、跑稳剩下的慢慢来。
返回列表