Skip to content中间件与
Express.js 概述
概述
在 Node.js 中,如果只使用 http 模块处理 Web 请求,路由分发只能靠手动解析。每次请求到达时,开发者必须自行读取 req.url 并判断 req.method,然后用条件分支匹配对应的动作。
javascript
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
return res.end('Hello World');
} else if (req.method === 'POST' && req.url === '/user') {
// 处理 POST /user
}
// 其余路径与方法...
});这段代码可以运行,但随着路径数量增加,嵌套的条件分支会迅速膨胀。除此之外,像托管静态文件、解析请求体、管理 Cookie 这类常见的 Web 开发任务,也都需要从头实现。MDN 的文档也指出,Node 本身并未对“不同 HTTP 动词”、“不同 URL 路径”、“静态文件”等任务提供内置支持——要么自行实现,要么引入专门的 Web 框架。
Express 正是为了解决这类重复劳动而出现的。它内部仍然依赖于 http 模块,但将路由分发、中间件处理和 HTTP 工具方法封装成了更结构化的接口。借助 Express,开发者可以按照 HTTP 方法与路径模式来定义处理函数,并且在请求-响应链中插入任意可复用的中间件。
基本用法
初始化项目与安装
首先确认 Node.js 与 npm 已安装。在终端中运行 node -v 和 npm -v 检查版本。
bash
mkdir myapp
cd myapp
npm init -y # 以默认配置创建 package.json
npm install expressnpm init -y 会跳过交互式询问,生成一个基础的 package.json。随后 npm install express 会将 Express 下载到 node_modules/ 目录,并在 package.json 的 dependencies 中添加相应记录。不需要额外的脚手架,项目的基础结构便已就绪。
Hello World 示例
在项目根目录创建 app.js,编写以下内容:
javascript
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('服务器已启动:http://localhost:3000');
});require('express') 返回 Express 模块的主入口。调用 express() 会创建一个应用实例,习惯上命名为 app。该实例提供了一系列与 HTTP 动词对应的方法,如 app.get、app.post、app.put 等。这里的 app.get('/') 表示:当收到对根路径 '/' 的 GET 请求时,执行后面的回调函数。
回调函数接收的 req 和 res 并非 Node.js 原生的请求/响应对象,而是 Express 在它们之上封装的增强版本。res.send() 负责结束响应并将文本内容发回客户端。
app.listen(3000, cb) 内部会调用 http.createServer() 并将 app 自身作为请求处理函数传入,随后监听指定端口。第二个参数是可选的启动回调。
运行 node app.js,在终端看到提示后,浏览器访问 http://localhost:3000,页面上会显示 “Hello World!”。使用 curl 也可验证:
bash
$ curl http://localhost:3000
Hello World!请求与响应对象
路由回调中出现的 req 与 res 是理解 Express 行为的关键。req 代表接收到的 HTTP 请求,可以从中获取请求方法、URL、请求头等信息;res 代表将要发出的 HTTP 响应,提供了一系列用于构造响应的方法。
例如,可以在刚才的路由中添加一行日志:
javascript
app.get('/', (req, res) => {
console.log(`请求方法: ${req.method}, 路径: ${req.url}`);
res.send('Hello World!');
});再次请求 /,终端会打印类似 请求方法: GET, 路径: / 的信息。req.method 和 req.url 是其中最基本的两个属性,后续章节会涉及更丰富的用法(如提取参数、解析请求体等)。
res.send() 的行为会随着参数类型而变化:传入字符串时,响应的 Content-Type 默认设为 text/html;传入对象时,会自动序列化为 JSON 并设置 Content-Type 为 application/json。其他常用的响应方法还包括 res.status() 设置状态码、res.json() 显式返回 JSON 等。
中间件与 next() 函数
Express 中还有一种名为“中间件”的处理函数。它的签名与路由回调类似,但多了一个 next 参数——一个函数,调用它会将控制权交给下一个能够匹配当前请求的处理函数。如果不调用 next() 且未发送响应,请求便会一直挂起。
一个典型的中间件用法是借助 app.all() 为某个路径匹配所有 HTTP 动词:
javascript
app.all('/secret', (req, res, next) => {
console.log('访问私有路径 ...');
next(); // 将控制权传递给下一个处理器
});每当有请求命中 /secret,日志会被输出,然后 next() 将请求交予后续的处理器。如果后续不再有其他处理函数直接发送响应,Express 最终会返回一个 404 状态。
需要注意,任何路由回调(包括 app.get、app.post 等)本质上也是特殊的中间件。只是绝大多数路由会直接发送响应,因此通常不会主动调用 next。中间件的执行顺序就是它们在代码中被注册的顺序。
请求-响应循环
一个 HTTP 请求在 Express 应用中的生命周期大致如下:
- 客户端发出请求。
- Express 从最先注册的中间件开始,依次检查是否匹配当前请求的路径(及方法)。
- 匹配的中间件可对
req和res执行操作,也可直接结束响应;如果调用了next(),流程将转向下一个匹配的中间件。 - 一旦某个处理器调用了
res.send()、res.json()等以发送响应,HTTP 响应便返回给客户端。此时后续代码仍会执行,但已无法再修改已发送的头部或主体。 - 若所有匹配的中间件均未结束响应,Express 最终会返回一个 404 状态码和内置的错误页面。
这个流程是后续理解错误处理、静态文件中间件以及更复杂的中间件组合方式的基础。
小结
本章从 Node 原生 http 模块的局限出发,解释了 Express 的设计动机;随后通过 npm init 和 npm install express 完成了项目初始化,编写了返回 “Hello World” 的最小服务器;接着介绍了 req/res 对象的角色,以及 next() 在中间件链中的基本作用;最后梳理了完整的请求-响应循环。
下一篇将在这个基础上进一步展开中间件的注册与调用顺序,并引入路由参数和 express.Router 的模块化路由,使应用结构更加清晰。
参考链接
- [1] https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction
- [7] https://learn.microsoft.com/zh-cn/visualstudio/javascript/tutorial-nodejs?view=visualstudio
- [9] https://expressjs.com/zh-cn/advanced/best-practice-performance
- [11] https://expressjs.com/en/advanced/best-practice-security
- [12] https://github.com/goldbergyoni/nodebestpractices/blob/master/README.chinese.md
