Express 简介

Express 中间件

理解中间件的工作原理,掌握 app.use、内置中间件、自定义中间件和路由级中间件的用法。

🎯 引言

中间件是 Express 的灵魂,几乎一切功能(解析 JSON、托管静态文件、日志、鉴权)都建立在它之上。学完这篇文章,你能说清楚中间件是什么、按什么顺序执行,会用内置中间件,也能写出自己的中间件。


🧱 什么是中间件

中间件就是一个函数,它能在请求到达路由之前(或之后)对请求做加工处理。

想象机场安检的流程:值机 → 安检 → 登机口 → 登机。旅客(请求)要经过一道道关卡(中间件),每一关检查完、盖个章(调用 next()),才能进入下一关。任何一关发现问题(比如没带证件),旅客就直接被拦下,后面的关卡都不用走了。

一个中间件函数固定接收三个参数:

const middleware = (req, res, next) => {
    // req:请求对象,可以读取和加工
    // res:响应对象,可以在这里直接结束请求
    // next:放行函数,调用后进入下一个中间件
};

🛠 全局中间件 app.use

app.use() 注册的中间件对所有请求生效。写一个日志中间件,打印每个请求的方法和地址:

app.js
import express from 'express';

const app = express();

// 日志中间件:每个请求进来都会先经过这里
app.use((req, res, next) => {
    console.log(`${req.method} ${req.url}`);
    next();
});

app.get('/', (req, res) => {
    res.send('首页');
});

app.listen(3000);

刷新几次浏览器,终端会打印 GET /。注意 next() 必须调用,否则请求就停在这个中间件里,浏览器会一直转圈等待。

中间件有两条出路:调用 next() 交给下一个环节,或者用 res.send() 等方法直接响应。每条请求路径上必须有其中之一,什么都不做会造成请求挂起。

🪤 执行顺序:自上而下

Express 严格按代码书写顺序依次执行中间件和路由,谁先命中谁先执行:

app.js
app.use((req, res, next) => {
    console.log('中间件 1');
    next();
});

app.use((req, res, next) => {
    console.log('中间件 2');
    next();
});

app.get('/', (req, res) => {
    console.log('路由处理');
    res.send('首页');
});

访问 /,终端依次打印 中间件 1中间件 2路由处理。这就像流水线上的工序,顺序调换了,结果可能就不对。比如把解析 JSON 的中间件写在路由后面,路由里就读不到请求体了。


🛠 内置中间件 express.json

POST 请求提交的 JSON 数据,原生 http 模块要自己监听数据流再拼接解析,很繁琐。Express 内置的 express.json() 中间件帮你把这一切做好,解析结果直接放在 req.body

app.js
const app = express();

// 解析 JSON 请求体,之后 req.body 就能用了
app.use(express.json());

app.post('/users', (req, res) => {
    console.log(req.body); // { name: '小明', age: 18 }
    res.send('创建成功');
});

终端用 curl 验证:

curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d "{\"name\":\"小明\",\"age\":18}"
不加 app.use(express.json()) 的话,req.bodyundefined。这是初学者的高频踩坑点:接口读不到数据,先检查这行有没有写、有没有写在路由前面。

🛠 内置中间件 express.static

图片、CSS、前端打包产物这类静态文件,不用一个个写路由,用 express.static 托管整个目录:

app.js
app.use(express.static('public'));

之后在项目里建一个 public 目录,放入 logo.png,浏览器直接访问 http://localhost:3000/logo.png 就能拿到。Vue 项目 npm run build 打包出的 dist 目录,也可以用这种方式让 Express 直接对外提供访问。


🛠 路由级中间件

app.use 对所有请求生效,但有些检查只想针对个别路由,比如“查看个人资料前必须登录”。把中间件写在路由的路径和处理函数之间即可:

app.js
// 鉴权中间件:检查请求头里有没有 token
const checkAuth = (req, res, next) => {
    if (!req.headers.token) {
        return res.status(401).send('请先登录');
    }

    next();
};

// 只有这条路由需要过安检
app.get('/profile', checkAuth, (req, res) => {
    res.send('个人资料');
});

请求 /profile 时先经过 checkAuth,没有 token 就直接返回 401,根本到不了处理函数;有 token 才放行。其他路由完全不受影响。


🧾 小节总结

  • 中间件是处理请求的函数,接收 reqresnext 三个参数,像流水线上的关卡。
  • app.use() 注册全局中间件,对所有请求生效,按代码顺序自上而下执行。
  • 中间件必须调用 next() 放行,或直接响应结束请求,否则请求会挂起。
  • express.json() 解析 JSON 请求体到 req.bodyexpress.static('public') 托管静态文件目录。
  • 把中间件写在路由的路径和处理函数之间,就是只对这条路由生效的路由级中间件。

❓ 知识问答

Q1:忘了调用 next() 会怎样?

请求就停在当前中间件,后面的路由永远执行不到,浏览器会一直等待直到超时。排查“接口没反应”时,先检查中间件是不是漏了 next()

Q2:中间件和路由处理函数有什么区别?

本质上是同一种函数。习惯上把“不结束请求、只做加工和检查”的叫中间件,把“最终返回响应”的叫路由处理函数。写在 app.use 里还是写在路由里,只影响生效范围。

Q3:express.json() 能解析表单吗?

不能,它只解析 Content-Type: application/json 的请求体。传统的表单提交(application/x-www-form-urlencoded)需要另一个内置中间件 express.urlencoded({ extended: true }),前后端分离项目里用得较少。

Q4:静态文件目录的名字一定要叫 public 吗?

不一定,express.static('dist')express.static('assets') 都可以,传什么目录名就托管哪个目录。叫 public 只是社区的习惯。


🧪 小练习

写一个统计请求耗时的中间件:请求进来时记录时间,响应结束后打印本次请求花了多少毫秒。

app.js
import express from 'express';

const app = express();

// 请在这里编写代码(提示:在 next() 前后分别取 Date.now(),利用 res 的 finish 事件在响应结束后计算)

app.get('/', (req, res) => {
    res.send('首页');
});

app.listen(3000);

🎉 恭喜你已经掌握了中间件这个 Express 的核心机制!下一篇我们系统梳理请求对象和响应对象上的常用 API。