Skip to content

Node系列 · Express:中间件

中间件(Middleware)是 Express 的灵魂——每个请求都会经过一连串中间件处理。理解中间件的"链式调用"和 next() 的控制权移交,就理解了 Express 90% 的工作方式。

一、什么是中间件

中间件是请求处理链上的一个环节——它能:

  • 读取 req(请求对象)并加工
  • 改写 res(响应对象)
  • 决定是否调用 next() 移交控制权
  • 提前 res.send() 结束响应

二、三种中间件

2.1 普通中间件(3 参数)

javascript
function logger(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next();   // 必须调用,否则请求会卡住
}

app.use(logger);

参数 (req, res, next)——next 是函数,调用它把控制权交给下一个中间件。

2.2 错误处理中间件(4 参数)

javascript
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: '服务器内部错误' });
});

4 个参数(多一个 err)让 Express 把它识别为错误中间件。要触发错误中间件,需要在前面调 next(err)

2.3 路由中间件(绑定到特定路径)

javascript
// 仅 /api/* 路径生效
app.use('/api', (req, res, next) => {
  console.log('API 请求');
  next();
});

也可以直接用 app.get(path, handler) / app.post(path, handler)——本质也是中间件。

三、next() 的三种行为

3.1 正常 next

javascript
app.use((req, res, next) => {
  console.log('中间件 A');
  next();   // 移交给下一个
});

3.2 next(err) 跳到错误处理

javascript
app.use((req, res, next) => {
  if (!req.headers.authorization) {
    return next(new Error('未授权'));
  }
  next();
});

app.use((err, req, res, next) => {
  res.status(401).json({ error: err.message });
});

3.3 不调 next(直接结束)

javascript
app.use((req, res, next) => {
  if (req.path === '/favicon.ico') {
    return res.status(204).end();   // 直接结束,不 next
  }
  next();
});

WARNING

不调 next() 也不 res.send()——请求会卡住直到超时。Express 默认 0 字节响应 + 超时。一定要保证每个分支都有出口。

四、中间件顺序

Express 按 app.use() 注册顺序执行:

javascript
app.use(logger);            // 1. 日志
app.use(express.json());    // 2. 解析 JSON body
app.use('/api', authCheck); // 3. /api/* 鉴权
app.use('/api/users', userRoutes);  // 4. 用户路由
app.use(notFound);          // 5. 404
app.use(errorHandler);      // 6. 错误处理

每条规则:

顺序中间件作用
1日志 / 性能监控记录每个请求
2body 解析express.json / express.urlencoded
3CORS / 安全头跨域、安全策略
4会话 / 鉴权检查登录状态
5业务路由实际 API
6404 处理未匹配路径
7错误处理捕获前面抛出的异常

五、实战:日志中间件

javascript
function requestLogger(req, res, next) {
  const start = Date.now();

  // 在响应结束时打印耗时
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
  });

  next();
}

app.use(requestLogger);

六、实战:鉴权中间件

javascript
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) {
    return res.status(401).json({ error: '未登录' });
  }

  try {
    const payload = jwt.verify(token, SECRET);
    req.user = payload;   // 挂到 req 上供后续中间件用
    next();
  } catch (err) {
    next(err);   // 触发错误中间件
  }
}

// 用法:保护 /api/private/*
app.use('/api/private', requireAuth);

七、async 中间件

Express 4.x 不会自动捕获 async 中间件抛出的错误。Express 5+ 才原生支持:

javascript
// Express 5(即将 GA):async 直接抛错会自动被错误中间件捕获
app.get('/api/users', async (req, res) => {
  const users = await User.findAll();
  res.json(users);
});

// Express 4.x:需要包装一层 catch
function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

app.get('/api/users', asyncHandler(async (req, res) => {
  const users = await User.findAll();
  res.json(users);
}));

八、最佳实践

场景推荐
通用中间件抽成单独文件,统一在 app.js 注册
鉴权中间件挂在 /api/* 路径下,业务路由才生效
错误处理始终放在最后注册
async 中间件Express 5 直接写 async;4.x 用 wrapper
第三方中间件app.use() 引入前先看文档
调试中间件morgan(HTTP 日志)/ helmet(安全头)

九、小结

  • 中间件是请求处理链的一个环节;签名 (req, res, next)
  • 错误中间件特殊:4 个参数 (err, req, res, next)
  • next() 移交;next(err) 跳错误处理;不调 next 直接 res.send 结束
  • 注册顺序就是执行顺序:日志 → body → CORS → 鉴权 → 路由 → 404 → 错误
  • async 中间件:Express 5 原生支持;4.x 用 asyncHandler wrapper
  • 中间件的本质是函数组合(chain of responsibility),是 Express 灵活性的来源