尧图精选

使用 Express 构建 REST API:前后端分离架构下的 JSON 输出与 CORS 跨域实践

🕒 发布时间:2026/9/15 17:29:57 📁 来源:尧图网络
使用 Express 构建 REST API前后端分离架构下的 JSON 输出与 CORS 跨域实践【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本指南以 Node.js 全栈课程中 nodeJS/apis/api_basics.md 为核心系统讲解如何让 Express 应用从渲染 HTML 模板转型为输出 JSON 的 REST API包括 REST 资源型端点设计、HTTP 动词的 CRUD 对应关系以及前后端分离部署时必须掌握的 Same-Origin Policy 与 CORS 跨域配置。读完本文你将能够独立设计 RESTful 路由、用res.json替代模板渲染、并在 Express 中全局或按路由启用 CORS、按白名单限制跨域来源。为什么选择前后端分离模块化与多端复用近年来网站开发中出现了一种新趋势不再把数据库与视图模板放在同一个应用里而是将后端与数据库托管在服务器上用前端静态托管服务如 GitHub Pages、Netlify 等单独部署前端。这种前后端分离的组织方式常被称为 Jamstack 架构。这样组织项目有两大好处模块化业务逻辑与视图逻辑解耦项目结构更清晰、更易维护。多端复用一套后端可以同时服务于多个前端例如一个网站、一个桌面应用或一个移动 App各自通过 HTTP 请求消费同一份数据接口。同时这种模式也让开发者可以自由选用 React、Vue 等前端框架构建纯前端单页应用SPA。前后端应用之间通常使用JSON作为通信格式。如果你已经学完前端 JavaScript 课程对fetch与 JSON 处理应该并不陌生可回顾 javascript/asynchronous_javascript_and_apis/working_with_apis.md。因此本课的核心其实只有一个问题如何让 Express 应用说出 JSON而不是 HTML。第一步让 Express 输出 JSON 而不是 HTML在传统 MVC 应用中Express 的控制器通过res.render()把视图模板渲染成 HTML 返回给浏览器详见 nodeJS/express/controllers.md 对响应方法的讲解。而构建 API 时你只需要把信息传给res.json()即可——本质上就是res.render()渲染视图模板返回 HTMLres.send()通用响应方法根据传入数据自动设置Content-Typeres.json()明确以 JSON 形式响应始终把Content-Type设为application/json。从 controllers.md 中的实现说明可以知道一个细节res.json内部其实也是调用res.send但它强制 JSON 化——会自动把非对象值转换为 JSON而res.send只有在传入布尔值或对象包括数组时才会按 JSON 处理。因此在 API 场景下res.json是更明确、更合适的选择。一个最小可用的 Express JSON 接口长这样基础服务器搭建参见 nodeJS/express/introduction_to_express.mdconst express require(express); const app express(); // 返回 JSON 而非 HTML app.get(/posts, (req, res) { res.json([ { id: 1, title: Hello REST }, { id: 2, title: CORS explained }, ]); }); app.get(/posts/:postid, (req, res) { res.json({ id: req.params.postid, title: A single post }); }); const PORT process.env.PORT || 3000; app.listen(PORT, () { console.log(API listening on port ${PORT}); });解析 JSON 请求体客户端通过POST/PUT提交 JSON 数据时服务端需要先解析请求体才能从req.body读取数据。在 Express 4.16.0 之前这需要额外引入body-parser中间件而从 Express 4.16.0 起JSON 解析功能已内置在 Express 包中直接使用内置中间件即可app.use(express.json()); // 解析 application/json 请求体 app.use(express.urlencoded({ extended: true })); // 解析表单请求体 app.post(/posts, (req, res) { // req.body 已包含客户端提交的 JSON 字段 res.status(201).json({ created: req.body.title }); });REST组织 API 端点的方式API 的结构可以有多种形态。比如你可以把获取某篇帖子的所有评论写成/api/getAllPostComments/:postid也可以写成/api/posts/:postid/comments。约定优于随意——业界普遍采用RESTRepresentational State Transfer表征状态转移来组织 API它对应着 CRUD 操作Create、Read、Update、Delete。遵循 REST 这样的既有模式会让 API 更易维护也让其他开发者更容易集成软件开发很大程度上就是清晰沟通而遵循约定正是沟通顺畅的保障。REST 的完整技术定义相当复杂但在本课程场景下无状态statelessness、可缓存cacheability等大多数特性在使用 Express 输出 JSON 时已经天然满足。我们需要刻意设计的是如何组织端点的 URIUniform Resource Identifier。资源型 URI直接指向资源用 HTTP 动词表达动作REST API 是基于资源resource-based的。也就是说不要使用/getPostComments、/savePostInDatabase这类动词化命名而是直接指向资源本身例如博客文章post并用 GET、POST、PUT、DELETE 等 HTTP 动词表达要执行的动作。通常每个资源只需要两类 URI一个面向整个集合一个面向集合中的单个对象。例如GET /posts获取博客文章列表GET /posts/:postid获取单篇文章。集合还可以嵌套要获取某篇文章下的评论列表访问/posts/:postid/comments要获取其中某条评论则访问/posts/:postid/comments/:commentid。URI 的每一段都指明一层资源。例如GET /posts返回全部文章GET /posts/:postid精确定位某篇文章GET /posts/:postid/comments返回该文章的评论列表GET /posts/:postid/comments/:commentid则定位到某篇具体文章的某条具体评论。HTTP 动词表VerbActionExamplePOSTCreatePOST /postsCreates a new blog postGETReadGET /posts/:postidFetches a single postPUTUpdatePUT /posts/:postidUpdates a single postDELETEDeleteDELETE /posts/:postidDeletes a single post可以看到CRUD 的四个字母与 HTTP 动词一一对应Create → POSTRead → GETUpdate → PUTDelete → DELETE。路由参数与查询参数在 Express 中实现上述 RESTful 端点需要借助路由参数。以:开头的路径片段即为路由参数Express 会自动把匹配到的值填入req.params对象详见 nodeJS/express/routing.md// GET /posts/42/comments req.params { postid: 42 } app.get(/posts/:postid/comments, (req, res) { console.log(req.params); res.json({ postid: req.params.postid, comments: [] }); }); // GET /posts/42/comments/7 req.params { postid: 42, commentid: 7 } app.get(/posts/:postid/comments/:commentid, (req, res) { console.log(req.params); res.json({ postid: req.params.postid, commentid: req.params.commentid }); });与路径参数不同查询参数query parameters以?开头、keyvalue成对出现、用分隔它不算路径的一部分更像传给某条路径的附加参数。Express 会自动解析查询参数并填入req.query重复的 key 会被合并成数组// GET /posts?sortdatedirectionascending app.get(/posts, (req, res) { console.log(Query:, req.query); // { sort: date, direction: ascending } res.json({ posts: [] }); });用 Router 组织 RESTful 路由回到本课引言中提到的经验在 nodeJS/express/routing.md 中我们把相关路由分组并抽取到各自独立的文件中这样修改某组路由时不会影响其他路由。构建 REST API 时同样如此——把资源相关的路由收敛到独立的 Router 里。例如为文章资源创建routes/postRouter.js// routes/postRouter.js const { Router } require(express); const postRouter Router(); postRouter.get(/, (req, res) res.json({ posts: [] })); postRouter.get(/:postid, (req, res) res.json({ id: req.params.postid }) ); postRouter.post(/, (req, res) res.status(201).json(req.body)); postRouter.put(/:postid, (req, res) res.json({ id: req.params.postid, updated: true }) ); postRouter.delete(/:postid, (req, res) res.json({ id: req.params.postid, deleted: true }) ); module.exports postRouter;然后在应用入口挂载到对应路径上Router 内的路径会继承父路径所以无需重复写/posts前缀// app.js const express require(express); const app express(); const postRouter require(./routes/postRouter); app.use(express.json()); app.use(/posts, postRouter); const PORT process.env.PORT || 3000; app.listen(PORT, () console.log(API listening on port ${PORT}));注意路由的定义顺序会影响匹配结果——Express 按注册顺序匹配因此通配或兜底路由应放在最后routing.md 中有专门的顺序很重要警示。同源策略与 CORSSame-Origin Policy浏览器的安全边界同源策略Same-Origin Policy是浏览器一项重要的安全机制它限制网页向与服务该页面的源不同的源发起请求。这里的源由协议protocol、域名domain和端口port共同决定——例如https://api.example.com与https://example.com即使域名相似也属于不同源。同源策略保护了用户数据但也带来一个现实问题在下一个项目中我们要把 REST API 和前端分别部署到不同的域名上。届时前端页面如https://my-frontend.example.com向 API如https://api.example.com发起的请求就是典型的跨源请求会被浏览器默认拦截。CORS服务器显式放行跨源访问解决办法是让服务器开启CORSCross-Origin Resource Sharing跨源资源共享明确允许特定前端访问其资源。CORS 的运作机制本质上是一组 HTTP 头的协商浏览器在跨源请求中携带Origin头声明请求来自哪个源服务器通过Access-Control-Allow-Origin响应头告知浏览器允许哪些源对于PUT、DELETE等非简单请求浏览器还会先发送一个OPTIONS预检preflight请求服务器需通过Access-Control-Allow-Methods、Access-Control-Allow-Headers等响应头声明允许的动词与请求头。如果服务器没有返回允许该源的 CORS 头浏览器就会拦截响应前端fetch便拿不到数据。在 Express 中启用 CORS全局与单路由Express 官方提供了cors中间件包安装后即可使用npm install cors全局启用所有路由都允许跨源访问const express require(express); const cors require(cors); const app express(); // 所有路由、所有来源均可跨源访问 app.use(cors());仅在单个路由上启用const cors require(cors); // 只有这个路由允许跨源访问其余路由仍受同源策略约束 app.get(/posts/:postid, cors(), (req, res) { res.json({ id: req.params.postid }); });生产环境只允许特定来源在开发阶段允许任何来源app.use(cors())可以让联调轻松很多。但对于任何真实项目一旦部署到生产环境你就应该只允许自己的前端域名访问 API其余来源一律拒绝。通过给cors传入配置对象把origin限制为白名单即可const cors require(cors); const corsOptions { origin: https://my-frontend.example.com, // 只允许该前端域名 }; app.use(cors(corsOptions));这样只有白名单内的源发出的请求才会在响应中拿到Access-Control-Allow-Origin其他源的跨源请求会被浏览器拦截从而在传输层之上再加一道防线。API 安全进阶Token 认证与 JWT本课主攻 REST 组织与 CORS 放行但只允许自己前端访问还不够——生产环境通常还要回答来的人是谁、有没有权限这个问题。课程配套的 nodeJS/apis/api_security.md 给出了 API 场景下的主流方案Token 认证。与传统的用户名 密码 Cookie 会话不同Token 认证的思路是用户登录成功后服务端签发一个安全令牌Token之后该用户的每一次请求都在请求头中携带这个 Token例如Authorization: Bearer token服务端验证通过才放行。这样做既避免了用户名密码反复传输也支持令牌过期以提升安全性。其中最常见的实现是JSON Web TokenJWT服务端用密钥对用户信息签名生成 JWT客户端保存它如存入 localStorage后续请求附带该 JWT服务端通过签名验签来决定放行或拒绝。验签逻辑通常封装为自定义中间件挂在需要保护的 RESTful 路由之前。实战Blog API 项目中的落地方式为了让理论落地课程配套项目 nodeJS/apis/project_blog_api.md 要求你构建一个纯 API 后端 两个独立前端读者端用于阅读和评论、作者端用于写作和发布的博客系统。这个项目的几个关键决策正好呼应本课内容模型设计先行先设计 Post、Comment、User 等模型与字段比如用某个字段标记已发布/未发布再用 Prisma 定义模型、搭建 Express 应用。RESTful 路由参考本课一个资源两种 URI 嵌套集合的方式组织 posts/comments 端点测试路由可以使用curl也可以使用 Postman 这类能直接发PUT/POST请求的工具浏览器地址栏只能发 GET。JWT 保护编辑路由登录成功签发 JWT客户端以Authorization头 Bearer模式携带服务端验签后决定是否允许编辑、发布等敏感操作防止陌生人篡改文章。前后端分别部署API 与前端部署到不同域名后前端fetchAPI 端点必然跨源——这正是本课 CORS 中间件发挥作用的地方。动手练习与知识自检练习建议阅读 RESTful API 设计的最佳实践文章注意其中用到的body-parser中间件在 Express 4.16.0 起已内置为express.json()无需再单独安装。阅读并动手跟做在 Express 中搭建 REST API的教程重点关注模块化代码组织、自定义中间件等内容。知识自检可回到上文对应小节核对REST 代表什么什么是 HTTP 动词它们为什么对 API 如此重要CRUDCreate、Read、Update、Delete中的每个字母分别对应哪个 HTTP 动词什么是同源策略Same-Origin Policy如何在 Express 应用中启用 CORS全局与单路由两种方式分别怎么写如何把 CORS 配置为只允许特定来源访问你的 API延伸阅读想继续深入本主题可以直接在仓库内按顺序研读nodeJS/express/routing.md路由参数、查询参数与 Router 组织方式的完整示例nodeJS/express/controllers.mdres.json/res.send/res.status等响应方法的实现细节与中间件机制nodeJS/apis/api_security.mdToken 认证与 JWT 在 Express API 中的应用nodeJS/apis/project_blog_api.md一个完整的 REST API 双前端实战项目javascript/asynchronous_javascript_and_apis/working_with_apis.md从前端视角看fetch与 API 交互理解 JSON 数据流的另一端。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →