错误处理中间件 #
一、错误处理概述 #
1.1 什么是错误处理中间件? #
错误处理中间件是Express中一种特殊的中间件,它有四个参数而不是三个:
javascript
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('出错了!');
});
1.2 错误处理中间件特点 #
| 特点 | 说明 |
|---|---|
| 四个参数 | err, req, res, next |
| 必须最后定义 | 放在所有路由之后 |
| 捕获所有错误 | 同步和异步错误 |
| 可以有多个 | 按顺序执行 |
二、基本用法 #
2.1 捕获同步错误 #
javascript
app.get('/error', (req, res) => {
throw new Error('出错了!');
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: err.message });
});
2.2 捕获异步错误 #
javascript
app.get('/async-error', async (req, res, next) => {
try {
const data = await someAsyncFunction();
res.json(data);
} catch (error) {
next(error);
}
});
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
2.3 Express 5.x 异步错误 #
Express 5.x 会自动捕获异步错误:
javascript
app.get('/async-error', async (req, res) => {
const data = await someAsyncFunction();
res.json(data);
});
三、自定义错误类 #
3.1 创建错误类 #
javascript
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(message = '资源未找到') {
super(message, 404);
}
}
class UnauthorizedError extends AppError {
constructor(message = '未授权') {
super(message, 401);
}
}
class ForbiddenError extends AppError {
constructor(message = '禁止访问') {
super(message, 403);
}
}
class ValidationError extends AppError {
constructor(message = '验证失败') {
super(message, 400);
}
}
module.exports = {
AppError,
NotFoundError,
UnauthorizedError,
ForbiddenError,
ValidationError
};
3.2 使用自定义错误 #
javascript
const { NotFoundError, UnauthorizedError } = require('./errors');
app.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('用户不存在');
}
res.json(user);
} catch (error) {
next(error);
}
});
app.get('/protected', (req, res, next) => {
if (!req.user) {
throw new UnauthorizedError('请先登录');
}
res.json({ user: req.user });
});
四、全局错误处理 #
4.1 基本错误处理 #
javascript
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
});
4.2 开发环境错误处理 #
javascript
const sendErrorDev = (err, res) => {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
};
const sendErrorProd = (err, res) => {
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
console.error('ERROR:', err);
res.status(500).json({
status: 'error',
message: '服务器内部错误'
});
}
};
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
sendErrorDev(err, res);
} else {
sendErrorProd(err, res);
}
});
4.3 完整错误处理中间件 #
javascript
const errorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
return res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
}
if (err.name === 'CastError') {
const message = `无效的 ${err.path}: ${err.value}`;
err = new AppError(message, 400);
}
if (err.code === 11000) {
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
const message = `重复的值: ${value},请使用其他值`;
err = new AppError(message, 400);
}
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(el => el.message);
const message = `无效的输入数据: ${errors.join('. ')}`;
err = new AppError(message, 400);
}
if (err.name === 'JsonWebTokenError') {
err = new AppError('无效的token,请重新登录', 401);
}
if (err.name === 'TokenExpiredError') {
err = new AppError('token已过期,请重新登录', 401);
}
if (err.isOperational) {
return res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
console.error('ERROR:', err);
return res.status(500).json({
status: 'error',
message: '服务器内部错误'
});
};
module.exports = errorHandler;
五、异步错误处理 #
5.1 asyncHandler包装器 #
javascript
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;
5.2 使用asyncHandler #
javascript
const asyncHandler = require('../utils/asyncHandler');
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.find();
res.json(users);
}));
app.post('/users', asyncHandler(async (req, res) => {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
}));
5.3 全局异步错误捕获 #
javascript
const express = require('express');
const app = express();
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
const routes = [
{ method: 'get', path: '/users', handler: async (req, res) => {
const users = await User.find();
res.json(users);
}},
{ method: 'post', path: '/users', handler: async (req, res) => {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
}}
];
routes.forEach(route => {
app[route.method](route.path, asyncHandler(route.handler));
});
六、404错误处理 #
6.1 基本404处理 #
javascript
app.use((req, res, next) => {
res.status(404).json({
status: 'fail',
message: `找不到 ${req.originalUrl}`
});
});
6.2 使用自定义错误 #
javascript
const { NotFoundError } = require('./errors');
app.use((req, res, next) => {
next(new NotFoundError(`找不到 ${req.originalUrl}`));
});
6.3 完整错误处理流程 #
javascript
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello');
});
app.use((req, res, next) => {
next(new NotFoundError(`找不到 ${req.originalUrl}`));
});
app.use((err, req, res, next) => {
res.status(err.statusCode || 500).json({
status: err.status || 'error',
message: err.message
});
});
七、错误日志记录 #
7.1 基本日志 #
javascript
app.use((err, req, res, next) => {
console.error('Error:', err.message);
console.error('Stack:', err.stack);
console.error('Request:', req.method, req.originalUrl);
res.status(err.statusCode || 500).json({
status: 'error',
message: err.message
});
});
7.2 使用winston日志 #
bash
npm install winston
javascript
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.Console()
]
});
app.use((err, req, res, next) => {
logger.error({
message: err.message,
stack: err.stack,
method: req.method,
url: req.originalUrl,
body: req.body,
query: req.query,
params: req.params,
user: req.user?.id
});
res.status(err.statusCode || 500).json({
status: 'error',
message: err.message
});
});
八、错误处理最佳实践 #
8.1 统一错误响应格式 #
javascript
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "验证失败",
"details": [
{ "field": "email", "message": "请输入有效的邮箱地址" }
]
}
}
8.2 错误码定义 #
javascript
const ERROR_CODES = {
VALIDATION_ERROR: 'VALIDATION_ERROR',
NOT_FOUND: 'NOT_FOUND',
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN',
INTERNAL_ERROR: 'INTERNAL_ERROR'
};
class AppError extends Error {
constructor(message, statusCode, code = ERROR_CODES.INTERNAL_ERROR) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
}
}
8.3 完整错误处理示例 #
javascript
const express = require('express');
const app = express();
app.use(express.json());
class AppError extends Error {
constructor(message, statusCode, code) {
super(message);
this.statusCode = statusCode;
this.code = code || 'ERROR';
this.isOperational = true;
}
}
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new AppError('用户不存在', 404, 'NOT_FOUND');
}
res.json(user);
}));
app.use((req, res, next) => {
next(new AppError(`找不到 ${req.originalUrl}`, 404, 'NOT_FOUND'));
});
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.code = err.code || 'INTERNAL_ERROR';
const response = {
success: false,
error: {
code: err.code,
message: err.message
}
};
if (process.env.NODE_ENV === 'development') {
response.error.stack = err.stack;
}
if (err.errors) {
response.error.details = err.errors;
}
console.error(`[${new Date().toISOString()}] ${err.code}: ${err.message}`);
res.status(err.statusCode).json(response);
});
module.exports = app;
九、总结 #
错误处理中间件要点:
| 概念 | 说明 |
|---|---|
| 四个参数 | err, req, res, next |
| 自定义错误 | 继承Error类 |
| 异步错误 | 使用asyncHandler |
| 404处理 | 放在路由之后 |
| 日志记录 | 记录错误详情 |
下一步,让我们学习常用中间件!
最后更新:2026-03-28