人人都会AI编程

常用中间件:静态资源、请求解析、跨域、日志

更新时间:2026-07-10

在 Express 中,中间件是处理请求的核心机制。一个 Express 应用本质上就是一层层中间件的串联,请求依次流经每个中间件,直到某个中间件发送响应或调用 next() 将控制权移交给下一个。Node.js 生态为最常见的后端需求提供了大量成熟的中间件,它们安装简单、配置直观,几乎每个 Web 服务都会用到。以下是最常用的四类。

1. 静态资源中间件 express.static

任何一个 Web 应用都需要向外提供静态文件——HTML、CSS、JS、图片、字体等。如果手写路由返回文件,需要自行处理 MIME 类型、缓存头、路径安全问题,容易出错且不必要。

Express 内置的 express.static 中间件专门解决这个问题。只需指定一个本地文件夹路径,它就能自动处理所有对静态资源的请求,并根据文件扩展名设置正确的 Content-Type,同时利用 Last-ModifiedETag 实现基础的协商缓存。

使用示例:

const express = require('express');
const app = express();

// 将 public 文件夹下的所有文件作为静态资源暴露
app.use(express.static('public'));

// 也可以指定虚拟路径前缀
app.use('/static', express.static('public'));

真实使用场景:

  • 前端构建后的产物(React/Vue 打包后的 dist 目录)可直接放在 public 中,由 Express 作为静态文件服务器提供服务。
  • 允许用户通过 URL /images/logo.png 直接访问服务器上的图片资源。

生产环境的注意点:

  • 不要暴露敏感目录(如 node_modules.env)。
  • 在生产环境中,通常会前置 Nginx 来处理静态文件,但开发环境和低流量场景下直接用 express.static 非常方便。
  • 可以传入一个选项对象来设置 maxAge(强缓存时间),例如 express.static('public', { maxAge: '1d' }) 来减少请求数。

2. 请求解析中间件 express.jsonexpress.urlencoded

HTTP 请求体中的数据并不会自动解析为 JavaScript 对象。曾经需要引入 body-parser 这个第三方包,但从 Express 4.16 开始,Express 内置了两个解析中间件:

  • express.json() —— 解析 Content-Type: application/json 的请求体,结果挂载到 req.body
  • express.urlencoded({ extended: true }) —— 解析表单提交(Content-Type: application/x-www-form-urlencoded),extended: true 允许使用 qs 库解析嵌套对象。

必须手动挂载,因为并非所有路由都需要解析请求体,Express 将选择权留给开发者。

使用示例:

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.post('/api/login', (req, res) => {
  console.log(req.body.username);
  console.log(req.body.password);
  res.send('ok');
});

常见配置问题:

  • 忘记挂载这两个中间件导致 req.bodyundefined 是新手最常遇到的坑。
  • express.urlencoded 主要用于兼容传统表单提交(如 <form> 的 POST),现代 SPA 应用通常只传 JSON,但仍建议保留它以兼容某些第三方回调或旧版客户端。
  • 如果接口需要接收原始二进制数据(如文件上传),则需要使用 multer 等专用中间件,不能用 express.raw() 草率处理。

3. 跨域中间件 cors

浏览器出于安全考虑,会阻止前端 JavaScript 从不同源(协议、域名、端口任一不同)发起的 HTTP 请求,这就是同源策略。后端需要在响应头中明确允许跨域,浏览器才会放行。

手动设置响应头虽然可以做到(例如 res.setHeader('Access-Control-Allow-Origin', '*')),但面对预检请求(OPTIONS)、自定义请求头、Cookie 凭证等场景时,代码会变得繁琐且容易遗漏。cors 中间件封装了所有跨域相关的逻辑,只需几行代码即可安全地配置 CORS 策略。

安装:

npm install cors

使用示例:

const cors = require('cors');

// 简单粗暴:允许所有来源跨域
app.use(cors());

// 生产环境应指定具体来源
app.use(cors({
  origin: 'https://www.example.com',
  methods: ['GET', 'POST'],
  credentials: true   // 允许携带 Cookie
}));

真实开发注意事项:

  • 开发环境下可以开启全放开的 cors(),方便前后端联调;但上线前务必限制 origin 为可信域名,避免被恶意站点利用。
  • 如果使用了 JWT 鉴权并将 token 存放在 Authorization 头,需要在 allowedHeaders 中加入该字段,否则预检请求会被拒绝。
  • cors 中间件默认会处理预检请求(OPTIONS),无需额外编写路由。

4. 日志中间件 morgan

在生产环境中,追踪每个请求的来源、时长、状态码是排查问题的基础。morgan 是一款轻量级的 HTTP 请求日志记录中间件,它提供多种预定义格式,也支持自定义日志格式。

安装:

npm install morgan

使用示例:

const morgan = require('morgan');

// 使用预定义的标准格式
app.use(morgan('dev'));
// 输出示例:GET /api/users 200 12.345 ms - 1234

// 线上使用 combined 格式,包含更多信息(IP、Referer、User-Agent)
app.use(morgan('combined'));

与日志框架集成:

morgan 默认将日志输出到控制台(stdout),在实际项目中通常会和 winstonpino 这类日志框架结合,以便将日志写入文件、发送到集中式日志平台。

const logger = require('./logger');  // 自定义 winston 实例

app.use(morgan('combined', {
  stream: { write: message => logger.info(message.trim()) }
}));

实用技巧:

  • 开发环境用 dev 格式,高亮不同状态码且简洁;线上用 combined 或自定义 token 记录请求处理时间、用户 ID 等业务信息。
  • 避免在日志中记录敏感数据(如密码、token),可以在自定义 token 时排除。
  • 日志中间件应放在路由之前,这样才能记录所有请求;但如果想要精确记录响应时间,需要注意它与错误处理中间件的顺序。

中间件的串联顺序

因为这四类中间件在任何 Web 服务中几乎都会同时使用,这里给出一个典型的 Express 项目入口文件顺序作为参考:

const express = require('express');
const cors = require('cors');
const morgan = require('morgan');

const app = express();

// 1. 日志中间件(最先注册,记录所有请求)
app.use(morgan('dev'));

// 2. 跨域中间件(尽早处理预检请求)
app.use(cors({ origin: 'http://localhost:3000' }));

// 3. 请求体解析中间件
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// 4. 静态资源中间件
app.use('/static', express.static('public'));

// 5. 路由
app.use('/api', require('./routes'));

// 6. 错误处理中间件(最后捕获异常)
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('服务器内部错误');
});

app.listen(3000);

这个顺序遵循了一个原则:通用性高、需要尽早处理的任务放在前面,具体业务路由放在后面,错误处理放在最后。 日志、跨域、请求体解析几乎每个请求都需要,因此理应前置;静态资源通常也需要在路由匹配前响应,避免被路由拦截。

掌握这四类中间件,基本就能搭建出一个健壮的 Express 服务骨架。在实际项目中,这些也是最先被引入和配置的基础设施,之后的业务逻辑都建立在这一层之上。