Express 入门:用 Node.js 后端框架搭建你的第一个服务器并理解请求生命周期

Express 入门:用 Node.js 后端框架搭建你的第一个服务器并理解请求生命周期 Express 入门用 Node.js 后端框架搭建你的第一个服务器并理解请求生命周期【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本指南是nodeJS/express课程的第一课对应仓库文档 introduction_to_express.md核心任务是带你从零搭建一个可运行的 Express 服务器并彻底理解一次 HTTP 请求从进入服务器到返回响应所经历的完整生命周期。学完本课你将掌握 Express 的初始化与启动、路由与中间件的底层工作方式以及如何让服务器在文件变更时自动重启——这些知识将直接支撑后续的 MVC 全栈应用与 REST API 开发详见 routing.md、controllers.md 等进阶课程。为什么在学会 Node 之后还需要 Express在上一阶段getting_started.md你已经通过 Node 原生的http模块、fs文件系统模块和URL类实现了一个可以读取文件、服务多页面网站的基础服务器。你会发现这套写法相当繁琐每次都要手动解析请求、路由分发、设置响应头。当业务复杂到需要管理表单、数据库、会话、错误处理时纯 Node 的样板代码会迅速失控。Express 正是在这个节点登场。它是一个**刻意保持极简barebones且不固执己见unopinionated**的后端框架它把请求解析、路由匹配、响应发送、静态资源服务等大量实现细节封装起来让你只关心业务逻辑同时它不做任何强制的架构约定你可以用自己想要的方式组织代码并且只按需引入需要的功能模块。这种设计带来的自由度很高但在多个可行方案之间做选择时也会有点难度这正是本课程后续各课要逐一解决的问题。在本课程中Express 将被用于两大方向使用MVCModel-View-Controller模式构建全栈应用参见 controllers.md 与 views.md构建REST API——就像你在 Weather App、React Shopping Cart 等项目中使用过的那些 API 一样参见 apis/api_basics.md。设置 Express创建你的第一个服务器初始化项目并安装依赖在一个新建的目录中首先运行npm init -y生成一个package.json然后安装 Express 依赖npm install express编写 app.js创建一个app.js文件作为 Express 服务器的入口。文件名可以随意比如index.js或main.js这里沿用 Express 官方文档的习惯命名。内容如下const express require(express); const app express(); app.get(/, (req, res) res.send(Hello, world!)); const PORT 3000; app.listen(PORT, (error) { // This is important! // Without this, any startup errors will silently fail // instead of giving you a helpful error message. if (error) { throw error; } console.log(My first Express app - listening on port ${PORT}!); });逐行拆解这段代码导入并初始化require(express)引入框架调用它得到app对象——这就是你的整个服务器实例。定义路由以app.get开头的那一行是一条route路由它告诉 Express「当收到针对/路径的 GET 请求时执行后面的回调函数」。路由的详细机制我们稍后再展开。监听端口app.listen(PORT, callback)让服务器在指定端口上监听入站请求。这里的localhost本质上就是本机自己的回环连接地址。回到终端运行node app.js一切正常的话你会看到My first Express app - listening on port 3000!如果出现问题比如端口已被占用由于我们在回调里对error做了显式抛出处理你会看到一条有用的错误信息而不是静默失败。恭喜你的第一个 Express 服务器已经跑起来了端口变量的正确写法上面的示例为了演示把端口硬编码成了3000。实际开发中端口号通常来自环境变量并在环境变量不存在时提供一个回退值const PORT process.env.PORT || 3000;这样做有两个实际好处当指定端口被占用时可以直接修改环境变量而无需改动源码许多托管服务PaaS会为你的应用配置专属端口这个端口可能与本地硬编码的值不同详见 deployment.md 中对实例与部署的讲解。一次请求的旅程从浏览器到响应发出一条 GET 请求服务器已在 3000 端口运行现在在浏览器地址栏访问http://localhost:3000/末尾的/即使忘记输入浏览器也会自动补上。这个动作的本质是浏览器向localhost:3000上监听的服务器也就是你的 Express 应用发送一条针对/路径的GET请求然后把收到的响应内容显示在窗口中。实际上你在地址栏访问任何网址都在做同一件事。访问https://theodinproject.com/paths等价于「向theodinproject.com发送一条针对/paths路径的GET请求并把响应渲染出来」。访问http://localhost:3000/后页面将显示Hello, world!。请求对象、中间件链与响应对象当 Express 收到这条GET请求时它会把请求封装进一个request 对象req然后让这个请求对象按顺序穿过一串我们称为「中间件函数」middleware functions的函数链直到某个中间件函数指示 Express 向请求作出响应为止。在我们的示例中请求是「GET /路径」恰好匹配app.js里定义的路由app.get(/, (req, res) res.send(Hello, world!));这条语句翻译成人话就是「如果一条 GET 请求到达/路径就把该请求传入下面这串中间件函数。」这里我们只注册了一个函数。关于路由匹配有两个关键点如果定义了多条路由Express 会把请求传给第一个同时匹配 HTTP 动词如GET与路径如/的路由路由的定义顺序至关重要——先定义的路由会先被匹配。Express 把回调函数执行时所需的两个对象作为参数传入第一个参数惯例命名为req是请求对象第二个参数惯例命名为res是response 对象。我们的回调调用res.send(Hello, world!)指示响应对象把字符串Hello, world!作为响应体发回。回调执行完毕、函数返回后由于 Express 已经被指示「对请求作出响应」它会结束这次请求-响应周期request-response cycle。与此同时浏览器收到响应并把它显示在屏幕上。res.send几乎可以发送任何内容甚至可以通过res.sendFile直接发送一个文件给客户端。路由基础动词、路径、参数app.get(/, handler)只是路由的冰山一角。在 routing.md 中路由被定义为「将请求的 HTTP 动词如GET/POST与 URL 路径匹配到一组中间件函数即控制器」的机制。这里先掌握最基本的几个概念HTTP 动词与路径每个 HTTP 动词都有对应的 Express 路由方法app.get(/, (req, res) res.send(Hello, world!)); app.post(/messages, (req, res) res.send(This is where you can see any messages.));app.get只匹配 GET 请求app.post只匹配 POST 请求——向/messages发 GET 请求不会命中 POST 路由可以用app.all()让一个路由匹配所有动词课程中主要使用GET仅用于从服务器获取数据和POST用于向服务器提交数据如表单后续 REST API 阶段还会遇到PUT、DELETE路径参数既可以是字符串也可以是正则表达式。字符串路径还支持{}让字符可选如/message{s}同时匹配/message与/messages支持*通配符splat必须后跟名字如/{*splat}可作自定义 404 的兜底路由。路由参数与查询参数路由参数以:开头的路径段例如app.get(/:username/messages, ...)。Express 会把实际值自动填充到req.params对象中GET /odin/messages时req.params为{ username: odin }查询参数URL 中?之后以keyvalue形式、分隔的部分不属于路径本身。Express 自动解析后填充到req.query重复的键会合并成数组如?sortdatesortlikes得到query.sort [date, likes]。app.get(/:username/messages, (req, res) { console.log(Params:, req.params); console.log(Query:, req.query); res.end(); });提示查询参数在表单场景中也常被用到——当表单以GET方法提交时数据不会出现在req.body中而是以查询字符串形式进入req.query见 forms_and_data_handling.md 的搜索练习。中间件请求-响应周期中的核心概念中间件函数是 Express 最核心的概念。它运行在「入站请求」与「最终路由处理器」之间通常接收三个参数req请求对象res响应对象next把控制权移交给链条中下一个中间件函数的函数可选详见 controllers.md 对next的四种调用方式——无参、传错误、next(route)、next(router)的说明。参数名只是惯例你可以随意命名request、response等。一个中间件函数可以修改请求或响应对象很多包借此在req上添加属性或设置模板渲染用的res.locals执行附加代码如验证请求、鉴权等调用next()移交控制权结束请求-响应周期一旦结束链条中剩余的中间件即使存在也不会再执行。应用级与路由级中间件应用级中间件通过app.use或app.METHOD绑定到整个 Express 实例对匹配路径的每个请求执行。不指定路径时默认匹配/即所有请求。通常放在应用代码最顶部保证最先执行。Express 内置了几个常用中间件例如express.json、express.urlencoded解析请求体到req.body表单处理时app.use(express.urlencoded({ extended: true }))是标准配置见 project_mini_message_board.mdexpress.static(public)以指定目录为根服务静态文件见 views.md 的静态资源部分路由级中间件通过router.use或router.METHOD绑定到某个 Express Router 实例只在请求匹配并穿过该路由时才执行。这让我们可以把路由分组拆到独立文件如routes/authorRouter.js再用app.use(/authors, authorRouter)挂载——这也正是 MVC 项目组织方式的基础。顺序决定一切Express按照中间件定义/注册的顺序执行。例如function myMiddleware(req, res, next) { console.log(Middleware function called); req.customProperty Hello from myMiddleware; next(); } app.use(myMiddleware);此例中中间件记录日志、给请求对象添加自定义属性、然后调用next()放行。链条中后续的中间件都能读到req.customProperty。反之如果某个修改请求对象的中间件被定义得太靠后它之前的中间件就无法看到它的改动。所以注册顺序必须谨慎安排——这也是错误处理中间件必须放在应用代码末尾的原因。另一个容易踩的坑res.send等响应方法只结束请求-响应周期不会终止函数执行。在res.send(Hello)之后再写res.send(Bye)会抛出「cannot send after sending」的错误。因此控制器在发送 404 响应后通常要显式return跳出见 controllers.md 的getAuthorById示例。响应方法一览在控制器中发送响应时有多种选择详见 controllers.md方法用途与特点res.send(body)通用响应方法根据传入数据自动设置Content-Type传入对象时会被序列化为 JSON 并设为application/jsonres.json(data)显式发送 JSON始终将Content-Type设为application/json是发送 JSON 时的首选res.json内部也调用res.send但会强制 JSON 序列化res.redirect(path)将客户端重定向到其他 URLres.render(view, locals)渲染视图模板并返回生成的 HTML配合 EJS 模板引擎使用见 views.mdres.status(code)手动设置状态码本身不会结束请求-响应周期需链式调用如res.status(404).send(...)省略时默认状态码为200此外还有res.sendFile可以响应一个文件——这正是你重写 Basic Informational Site 项目时要用的能力。文件变更时自动重启服务器用node app.js运行服务器时项目目录中任何 JavaScript/JSON 文件的修改不会自动生效除非你手动中断进程并重新运行。有两个方案可以避免这种手动操作Node 内置的 watch 模式推荐加上--watch标志即可例如node --watch app.js。Node 会监视app.js以及它最终依赖的所有文件检测到变更后自动重启服务器——这和使用 Webpack、Vite 开发服务器的体验一致。这也是本课程推荐的最简方案。Nodemon一个高度可配置、专门监听变更并自动重启的包。由于 Node 在早期并没有稳定的内置 watch 模式你很可能在各类项目里见到 Nodemon 的身影但既然内置方案已经可用直接用--watch标志更省事。实战用 Express 重写 Basic Informational Site原文档的作业要求你回到此前用纯 Node 完成的 Basic Informational Site 项目包含index.html、about.html、contact-me.html、404.html四个页面原本用http模块 手工 URL 判断实现安装 Express 后用几条app.get()重写它。改造后的核心思路是const express require(express); const app express(); const path require(node:path); // 为每个页面路径注册一条路由 app.get(/, (req, res) res.sendFile(path.join(__dirname, index.html))); app.get(/about, (req, res) res.sendFile(path.join(__dirname, about.html))); app.get(/contact-me, (req, res) res.sendFile(path.join(__dirname, contact-me.html))); // 兜底 404 路由必须放在所有具体路由之后 app.get(/{*splat}, (req, res) res.sendFile(path.join(__dirname, 404.html))); const PORT process.env.PORT || 3000; app.listen(PORT, (error) { if (error) throw error; console.log(Server listening on port ${PORT}!); });注意两点一是 404 兜底路由/{*splat}必须定义在所有具体路由之后否则会抢占所有请求路由顺序问题可回顾 routing.md二是用path.join(__dirname, ...)拼接绝对路径避免相对路径在不同启动目录下失效。完成本项目后你已具备继续学习下一课 routing.md路由进阶、controllers.md控制器、中间件与错误处理、views.mdEJS 模板的基础紧接着的 project_mini_message_board.md 将让你用 Express EJS 表单 POST 完成第一个真正可交互的全栈应用project_inventory_application.md 则要求你完整实践 MVC CRUD。沿着这条路径你就能一路走到 REST API 与部署上线deployment.md。小结与自检本课需要掌握的核心能力用npm init -ynpm install express初始化项目并写出可运行的app.js描述一条入站请求如何被封装为req对象、穿过中间件链、被路由匹配、最终由res.send等响应方法结束请求-响应周期说清楚什么是中间件函数以及注册顺序为何重要用node --watch app.js或 Nodemon实现文件变更后的自动重启理解端口应优先取自process.env.PORT并回退到默认值这对本地开发与云端部署都至关重要。练习建议花几分钟通读 Express 官方文档的 API 部分熟悉术语然后把上面 Basic Informational Site 的改写亲手跑通。从「纯 Node 手写路由」到「Express 声明式路由」的对比会让你对框架的价值与请求生命周期有更直观的体会。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考