Express 简介

Express 错误处理

掌握 Express 的 404 处理、错误处理中间件和统一错误响应格式,理解 Express 5 的异步错误自动捕获。

🎯 引言

代码总会出错:用户访问了不存在的地址、查询的数据找不到、程序抛了异常。学完这篇文章,你能给服务器配上 404 处理和统一的错误处理中间件,让任何意外都有得体的响应,而不是让服务直接崩溃。


🧱 为什么需要统一错误处理

如果不做任何处理,路由里抛出的异常会让 Express 返回一坨默认的 HTML 错误页,甚至直接把堆栈信息暴露给客户端。这对用户不友好,还可能泄露服务器内部细节。

我们希望的是:无论哪里出错,客户端都收到统一格式的 JSON 错误信息,前端可以统一拦截处理,比如弹一个提示框。


🛠 处理 404:接口不存在

当请求没有命中任何路由时,Express 默认返回一段 HTML。更常见的做法是注册一个兜底中间件,放在所有路由的后面:

app.js
import express from 'express';

const app = express();

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

// 404 兜底:放在所有路由之后,前面都没命中就会走到这里
app.use((req, res) => {
    res.status(404).json({ code: 404, message: '接口不存在' });
});

app.listen(3000);

访问 /abc 这样的任意地址,就会收到 JSON 格式的 404 响应。

404 中间件本质上是利用了中间件按顺序执行的机制:前面的路由都没响应,请求自然流到了最后。所以它的位置必须写在所有路由之后。

🧱 错误处理中间件:四个参数

普通中间件是三个参数,错误处理中间件固定四个参数,第一个参数是错误对象 err

app.js
// 错误处理中间件:必须有四个参数,Express 靠参数个数识别它
app.use((err, req, res, next) => {
    console.error('出错了:', err.message);

    res.status(500).json({ code: 500, message: '服务器开小差了,请稍后再试' });
});

Express 通过参数个数区分两种中间件,少写一个 next 参数,它就变成普通中间件,永远接不到错误。它的位置同样要放在所有路由之后。

路由里出错了,用 next(err) 把错误交给它:

app.js
app.get('/users/:id', (req, res, next) => {
    const user = null; // 假设数据库没查到

    if (!user) {
        // 把错误传给错误处理中间件
        return next(new Error('用户不存在'));
    }

    res.json(user);
});

✨ Express 5:异步错误自动捕获

路由处理函数是 async 的,里面抛错怎么办?这是 Express 5 和 4 的一个重要区别。

Express 4async 函数里抛错不会被自动捕获,必须手动 try/catchnext(err),否则进程可能直接崩溃。这是 Express 4 时代的著名踩坑点。

Express 5:框架会自动捕获 async 路由里抛出的错误和 rejected 的 Promise,直接转交错误处理中间件,不需要手动 next(err)

app.js
app.get('/users', async (req, res) => {
    // Express 5 中,这个错误会自动进入错误处理中间件
    throw new Error('数据库查询失败');
});

配上前面的错误处理中间件,访问 /users 会收到统一的 500 JSON 响应,服务也不会崩。

自动捕获只针对 async 函数里的异常。同步回调里(比如 setTimeout 里)抛出的错误依然捕获不到,那种场景需要自己在回调里 try/catch 后调用 next(err)。日常开发把路由写成 async 函数就能覆盖绝大多数情况。

🛠 统一错误响应格式

把所有拼起来,给项目定一套统一的响应约定:

  • 成功:{ code: 0, data: 数据 }
  • 失败:{ code: 错误码, message: '提示信息' }
app.js
import express from 'express';

const app = express();

app.use(express.json());

app.get('/users/:id', async (req, res) => {
    const id = Number(req.params.id);

    if (id !== 1) {
        // 抛出错误,交给错误处理中间件统一处理
        throw new Error('用户不存在');
    }

    res.json({ code: 0, data: { id: 1, name: '小明' } });
});

// 404 兜底
app.use((req, res) => {
    res.status(404).json({ code: 404, message: '接口不存在' });
});

// 统一错误处理
app.use((err, req, res, next) => {
    console.error('出错了:', err.message);

    res.status(500).json({ code: 500, message: err.message });
});

app.listen(3000);

有了这套结构,路由里只管抛错,响应格式由错误处理中间件统一把控,前端拿到任何响应都能按同一个约定解析。


🧾 小节总结

  • 在所有路由之后注册兜底中间件,统一返回 JSON 格式的 404 响应。
  • 错误处理中间件固定四个参数 (err, req, res, next),Express 靠参数个数识别它。
  • 同步代码里用 next(err) 传递错误;Express 5 中 async 路由抛错会自动进入错误处理中间件,这是相比 Express 4 的明显改进。
  • 错误处理中间件必须写在所有路由之后,否则接不到前面路由抛出的错误。
  • 建议统一响应格式,成功和失败都有固定结构,方便前端统一处理。

❓ 知识问答

Q1:错误处理中间件里的 next 用不上,能删掉吗?

不能。Express 是靠函数参数的个数来识别错误处理中间件的,必须是四个参数。删掉 next 它就变成普通中间件,收不到任何错误。

Q2:next() 和 next(err) 有什么区别?

next() 是正常放行,交给下一个普通中间件或路由;next(err) 是报告出错,Express 会跳过后续所有普通中间件和路由,直接进入错误处理中间件。

Q3:抛错后服务器会挂掉吗?

只要错误进入了错误处理中间件,服务就不会挂,还能继续处理其他请求。真正危险的是 Express 5 里那些没被捕获的异步回调错误(如 setTimeout 里的异常),可能导致进程退出,需要自行 try/catch

Q4:404 和 500 的处理能合并成一个中间件吗?

不建议。404 是“没有路由命中”,错误处理是“路由执行中出错”,触发时机完全不同。分开写职责清晰,也是社区通用的做法。


🧪 小练习

/divide 接口加上错误处理:接收查询参数 ab,返回 a / b 的结果;当 b 为 0 时抛出错误,由错误处理中间件返回统一的错误 JSON。

app.js
import express from 'express';

const app = express();

app.get('/divide', async (req, res) => {
    // 请在这里编写代码
});

// 请在这里编写 404 兜底和错误处理中间件

app.listen(3000);

🎉 恭喜你已经掌握了 Express 的错误处理!下一篇是综合实战:用前面所有知识搭建一套完整的 RESTful 用户接口。