Node.js 中间件:自定义中间件与错误处理
在 Express 应用中,中间件是处理请求-响应周期的核心机制。创建自定义中间件能让你灵活控制请求流程,而正确处理错误则确保应用稳定运行。以下步骤将手把手教你实现这两项关键能力。
编写基础自定义中间件
-
新建一个
.js文件(例如logger.js),并在其中导出一个函数。该函数接收三个参数:req(请求对象)、res(响应对象)和next(传递控制权的函数)。// logger.js function logger(req, res, next) { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next(); // 必须调用 next() 才能继续后续处理 } module.exports = logger; -
在主应用文件(如
app.js)中引入这个中间件,并注册到 Express 实例。const express = require('express'); const logger = require('./logger'); const app = express(); app.use(logger); // 对所有请求启用日志中间件 app.get('/', (req, res) => { res.send('Hello World'); }); app.listen(3000); -
启动应用后,每次访问任意路由,控制台都会打印带时间戳的请求日志。
编写带条件逻辑的中间件
中间件可以基于请求内容做判断,决定是否继续执行或提前终止。
-
创建一个验证 API 密钥的中间件
apiKeyValidator.js:// apiKeyValidator.js function apiKeyValidator(req, res, next) { const apiKey = req.headers['x-api-key']; if (!apiKey || apiKey !== 'my-secret-key') { return res.status(401).json({ error: 'Invalid or missing API key' }); } next(); // 验证通过,继续 } module.exports = apiKeyValidator; -
仅对特定路由组启用该中间件,避免影响公开接口:
const express = require('express'); const apiKeyValidator = require('./apiKeyValidator'); const app = express(); // 公开路由不受影响 app.get('/public', (req, res) => { res.json({ message: 'This is public' }); }); // 受保护路由需验证密钥 app.use('/api', apiKeyValidator); app.get('/api/data', (req, res) => { res.json({ data: 'Protected content' }); }); -
测试时,访问
/api/data若未提供正确x-api-key头,将收到401错误;而/public始终可访问。
实现统一错误处理中间件
Express 要求错误处理中间件必须显式接收四个参数(err, req, res, next),即使不使用 next。
-
编写全局错误处理器
errorHandler.js:// errorHandler.js function errorHandler(err, req, res, next) { console.error(err.stack); // 记录错误堆栈 // 开发环境返回详细错误,生产环境隐藏敏感信息 const isDevelopment = process.env.NODE_ENV === 'development'; res.status(err.status || 500).json({ error: { message: err.message, ...(isDevelopment && { stack: err.stack }) } }); } module.exports = errorHandler; -
在应用末尾注册此中间件(必须放在所有路由之后):
const express = require('express'); const errorHandler = require('./errorHandler'); const app = express(); // 模拟一个会抛出错误的路由 app.get('/error', (req, res, next) => { const err = new Error('Something went wrong!'); err.status = 500; next(err); // 通过 next(err) 触发错误处理 }); // 必须放在最后! app.use(errorHandler); app.listen(3000); -
触发错误时(如访问
/error),客户端会收到结构化 JSON 响应,同时服务器记录完整错误堆栈。
处理异步操作中的错误
异步代码(如数据库查询)中的错误不会自动传递给 Express,需手动捕获。
-
封装一个辅助函数
asyncHandler,自动捕获 Promise 拒绝:// asyncHandler.js function asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; } module.exports = asyncHandler; -
在路由中使用此包装器,避免重复写 try-catch:
const express = require('express'); const asyncHandler = require('./asyncHandler'); const app = express(); // 模拟异步操作失败 app.get('/async-error', asyncHandler(async (req, res) => { throw new Error('Async operation failed!'); })); app.use(require('./errorHandler')); // 复用之前的错误处理器 -
任何被拒绝的 Promise都会自动传递给错误处理中间件,无需手动调用
next(err)。
中间件执行顺序与注意事项
中间件按注册顺序执行,错误处理中间件必须位于最后。以下是关键规则:
| 场景 | 正确做法 | 错误做法 |
|---|---|---|
| 提前终止请求 | 直接返回响应(如 res.send()) |
调用 next() 后又发送响应 |
| 传递错误 | 调用 next(err) |
抛出未捕获异常(同步代码除外) |
| 异步错误 | 用 asyncHandler 包装 或 手动 catch |
直接在 async 函数中抛错而不处理 |
始终确保每个中间件要么发送响应,要么调用 next(),否则请求会挂起直至超时。

暂无评论,快来抢沙发吧!