Express学习笔记(二)——Express中间件
目录
1. 中间件的概念
1.1 什么是中间件
1.2 现实生活中的例子
1.3 Express 中间件的调用流程
1.4 Express 中间件的格式
1.5 next 函数的作用
2. Express 中间件的初体验
2.1 定义中间件函数
2.2 全局生效的中间件
2.3 定义全局中间件的简化形式
2.4 中间件的作用
2.5 定义多个全局中间件
2.6 局部生效的中间件
2.7 定义多个局部中间件
2.8 了解中间件的5个使用注意事项
3. 中间件的分类
3.1 应用级别的中间件
3.2 路由级别的中间件
3.3 错误级别的中间件
3.4 Express内置的中间件
3.5 第三方的中间件
4. JSON与URL-encoded请求体格式详解(补充)
4.1 JSON 格式(application/json)
4.2 URL-encoded 格式(application/x-www-form-urlencoded)
5. 自定义中间件
5.1 需求描述与实现步骤
5.2 定义中间件
5.3 监听 req 的 data 事件
5.4 监听 req 的 end 事件
5.5 使用 querystring 模块解析请求体数据
5.6 将解析出来的数据对象挂载为 req.body
5.7 将自定义中间件封装为模块
1. 中间件的概念
1.1 什么是中间件
1.2 现实生活中的例子

1.3 Express 中间件的调用流程

1.4 Express 中间件的格式

- 中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。
1.5 next 函数的作用

2. Express 中间件的初体验
2.1 定义中间件函数

2.2 全局生效的中间件

2.3 定义全局中间件的简化形式
2.4 中间件的作用

const express = require('express')
const app = express()
// 定义一个全局的中间件
app.use((req, res, next) => {
// 获取到请求到达服务器的时间
const time = Date.now()
// 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time
next()
})
app.get('/', (req, res) => {
res.send('home page,' + req.startTime)
})
app.get('/user', (req, res) => {
res.send('user page,' + req.startTime)
})
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
2.5 定义多个全局中间件

2.6 局部生效的中间件

2.7 定义多个局部中间件

2.8 了解中间件的5个使用注意事项
- ① 一定要在路由之前注册中间件
- ② 客户端发送过来的请求,可以连续调用多个中间件进行处理
- ③ 执行完中间件的业务代码之后,不要忘记调用 next() 函数
- ④ 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
- ⑤ 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
3. 中间件的分类
- ① 应用级别的中间件
- ② 路由级别的中间件
- ③ 错误级别的中间件
- ④ Express 内置的中间件
- ⑤ 第三方的中间件
3.1 应用级别的中间件

3.2 路由级别的中间件

3.3 错误级别的中间件
- 专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
- 错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。
- 错误级别的中间件,必须注册在所有路由之后!
演示错误级别的中间件:
const express = require('express')
const app = express()
app.get('/', (req, res) => {
// 1.1 人为的制造一个错误
throw new Error('服务器内部发生了错误!')
res.send('home page')
})
// 2. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
3.4 Express内置的中间件
- ① express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
// 在这里调用 express.static() 方法,快速的对外提供静态资源
app.use('/abc', express.static('./files'))
app.use(express.static('./clock'))
- ② express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
- ③ express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
代码演示:
const express = require('express')
const app = express()
// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析表单中的 url-encoded 格式的数据
app.use(express.urlencoded({extended: false}))
app.post('/user', (req, res) => {
// 在服务器,可使用 req.body 这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
app.post('/book', (req, res) => {
// 在服务器端,可以通过 req.body 来获取 JSON 格式的表单数据 和 url-encoded 格式的数据
console.log(req.body)
res.send('ok')
})
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
3.5 第三方的中间件
- ① 运行 npm install body-parser 安装中间件
- ② 使用 require 导入中间件
- ③ 调用 app.use() 注册并使用中间件
const express = require('express')
const app = express()
// 1. 导入解析表单数据的中间件 body-parser
const parser = require('body-parser')
// 2. 使用 app.use() 注册中间件
app.use(parser.urlencoded({ extended: false}))
app.post('/user', (req, res) => {
// 如果没有配置任何解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body)
res.send('ok')
})
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
- Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的。
4. JSON与URL-encoded请求体格式详解(补充)
4.1 JSON 格式(application/json
)
-
定义与特点
- 数据格式:基于键值对的嵌套结构,支持对象、数组、字符串、数字、布尔值等数据类型。
- 编码方式:直接使用 Unicode 字符(如 UTF-8),无需对特殊字符(如
@
、空格
)进行转义。 - 可读性:结构化清晰,适合人类阅读和调试。
-
语法示例
{
"name": "Alice",
"age": 28,
"isStudent": false,
"hobbies": ["coding", "music"],
"address": {
"city": "Shanghai",
"postcode": "200000"
}
}
-
适用场景
- 复杂数据:需要传递嵌套对象或数组(如配置信息、用户资料)。
- 前后端分离:现代 Web 应用和移动端 API 交互。
- 非表单数据:如日志上报、批量操作、业务逻辑数据。
-
HTTP 请求示例
POST /api/users HTTP/1.1
Content-Type: application/json
{
"username": "alice",
"password": "p@ssw0rd"
}
-
前后端处理
-
前端发送
-
// 使用 Fetch API fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: 'alice', password: 'p@ssw0rd' }), }); // 注意:必须手动调用 JSON.stringify() 序列化数据
-
- 后端解析(以 Node.js/Express 为例):
-
const express = require('express'); const app = express(); // 需启用 JSON 解析中间件 app.use(express.json()); app.post('/api/users', (req, res) => { console.log(req.body.username); // 直接获取 JSON 数据 });
-
-
-
注意事项
-
Content-Type 必须正确:若未设置
application/json
,后端可能无法解析。 -
不支持文件上传:JSON 无法直接传输二进制文件(需用 Base64 编码或改用
multipart/form-data
)。 -
数据冗余:字段名重复时会占用额外空间(如数组中的多个对象)。
-
4.2 URL-encoded 格式(application/x-www-form-urlencoded
)
-
定义与特点
-
数据格式:扁平化的键值对,用
&
分隔多个参数,格式为key1=value1&key2=value2
。 -
编码方式:特殊字符(如空格、
@
、:
)需转义为%XX
形式(如空格 →%20
,@
→%40
)。 -
可读性:编码后的字符串难以直接阅读。
-
- 语法示例
name=Alice&age=28&isStudent=false&hobbies=coding&hobbies=music&address.city=Shanghai&address.postcode=200000
- 注意:
- URL-encoded 本身不支持嵌套对象或数组,但可通过约定实现(如
address.city
表示嵌套字段)。
- URL-encoded 本身不支持嵌套对象或数组,但可通过约定实现(如
-
适用场景
-
HTML 表单提交:浏览器默认的表单编码方式。
-
简单键值对:无嵌套结构的参数(如登录、搜索)。
-
兼容旧系统:传统 API 或需要 URL 参数复用的场景(如 GET 请求的 Query String)。
-
-
HTTP 请求示例
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=alice&password=p%40ssw0rd
前后端处理
-
前端发送:
-
// 使用 URLSearchParams 自动编码 const params = new URLSearchParams(); params.append('username', 'alice'); params.append('password', 'p@ssw0rd'); fetch('/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params, }); // 或手动拼接字符串(需注意转义) body: 'username=alice&password=p%40ssw0rd'
-
- 后端解析(以 Node.js/Express 为例):
-
const express = require('express'); const app = express(); // 需启用 URL-encoded 解析中间件 app.use(express.urlencoded({ extended: true })); // extended: true 允许解析嵌套对象 app.post('/login', (req, res) => { console.log(req.body.username); // 直接获取参数 });
-
- 注意事项
- 编码必须正确:前端需处理特殊字符转义(如
@
→%40
),否则后端可能解析错误。 - 不支持复杂结构:嵌套对象或数组需通过约定(如
hobbies[]=coding&hobbies[]=music
)或后端框架扩展(如express.urlencoded({ extended: true })
)。 - 数据体积较大:重复的键名和转义字符会增加传输量。
- 编码必须正确:前端需处理特殊字符转义(如
5. 自定义中间件
5.1 需求描述与实现步骤
- ① 定义中间件
- ② 监听 req 的 data 事件
- ③ 监听 req 的 end 事件
- ④ 使用 querystring 模块解析请求体数据
- ⑤ 将解析出来的数据对象挂载为 req.body
- ⑥ 将自定义中间件封装为模块
5.2 定义中间件

5.3 监听 req 的 data 事件
在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。

5.4 监听 req 的 end 事件
当请求体数据接收完毕之后,会自动触发 req 的 end 事件。
因此,我们可以在 req 的 end 事件中,拿到并处理完整的请求体数据。示例代码如下:
5.5 使用 querystring 模块解析请求体数据

5.6 将解析出来的数据对象挂载为 req.body

const express = require('express')
// 导入 node.js 内置的 querystring 模块
const qs = require('querystring')
const app = express()
// 这是解析表单数据的中间件
app.use((req, res, next) => {
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 3.监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str) //bookname=%E8%A5%BF%E6%B8%B8%E8%AE%B0&author=%E5%90%B4%E6%89%BF%E6%81%A9&gender=%E7%94%B7
// todo: 把字符串格式的请求体数据,解析为对象格式
const body = qs.parse(str)
console.log(body)
req.body = body
next()
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
app.listen(80, () => {
console.log('express server is running at http://127.0.0.1')
})
5.7 将自定义中间件封装为模块
