boom vs create-error vs http-errors vs http-status vs http-status-codes
Node.js 错误对象构建与 HTTP 状态码常量管理
boomcreate-errorhttp-errorshttp-statushttp-status-codes类似的npm包:

Node.js 错误对象构建与 HTTP 状态码常量管理

这五个 npm 包主要解决后端开发中的两个核心问题:如何规范地创建带有 HTTP 状态码的错误对象,以及如何安全地引用 HTTP 状态码常量。http-errors 是 Express 生态的标准错误创建工具;boom 曾是 hapi 框架的核心组件但已弃用;create-error 用于生成通用错误构造函数。而 http-status 和 http-status-codes 则专注于提供状态码的数字与文本映射,避免硬编码魔法数字。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
boom02,922-68 年前BSD-3-Clause
create-error0101-513 年前MIT
http-errors01,56119.1 kB201 年前MIT
http-status0483338 kB52 年前BSD-3-Clause
http-status-codes01,121223 kB323 年前MIT

Node.js 错误处理与状态码管理:五大方案深度对比

在构建 Node.js 后端或全栈应用时,如何规范地处理 HTTP 错误以及管理状态码是架构设计的基础环节。boom、create-error、http-errors 专注于错误对象的创建,而 http-status 和 http-status-codes 则致力于消除魔法数字。虽然它们看似功能重叠,但定位截然不同。本文将从工程实践角度,对比它们的 API 设计、维护状态及适用场景。

⚠️ 核心分类与维护状态

首先必须明确,这五个包分为两类,且维护状态差异巨大。

第一类:错误对象创建

  • http-errors:活跃维护。Express 官方推荐,社区标准。
  • boom:已弃用。原 hapi 生态组件,已迁移至 @hapi/boom。
  • create-error:低频维护。通用错误构造函数生成器,非 HTTP 专用。

第二类:状态码常量映射

  • http-status-codes:活跃维护。类型支持好,前端后端通用性强。
  • http-status:维护中。功能类似,但包体积和导出方式略有不同。

💡 重要提示:boom 包在 npm 上已标记为 Deprecated。新项目中严禁直接使用 boom,应改用 @hapi/boom 或 http-errors。下文对比仅作为历史代码维护参考。

🛠️ 错误对象创建:API 设计与集成

在 Express 或 Koa 中间件中,我们需要抛出带有状态码的错误,以便全局错误处理器捕获。

http-errors 是最直接的工具,它直接返回一个带有 status 属性的 Error 对象。

// http-errors: 直接创建带状态码的错误
const createError = require('http-errors');

// 方式 1:传入状态码和消息
throw createError(404, '用户未找到');

// 方式 2:使用语义化方法
throw createError.NotFound('用户未找到');

boom (及 @hapi/boom) 倾向于返回一个包含 output 属性的复杂对象,原本设计用于 hapi 的响应装饰。

// boom: 生成装饰性错误对象 (已弃用,仅供参考)
const Boom = require('boom');

// 返回一个包含 response 信息的对象
const err = Boom.notFound('用户未找到');
// err.output.statusCode === 404
// err.output.payload 包含格式化后的响应数据

create-error 不直接处理 HTTP 状态码,而是用于创建自定义错误类型构造函数。

// create-error: 创建自定义错误类
const createError = require('create-error');

// 定义一个业务错误类型
const ValidationError = createError('ValidationError');

// 实例化(需手动关联状态码)
const err = new ValidationError('数据格式错误');
err.status = 400; // 需要手动附加

🔢 状态码常量管理:类型安全与可读性

硬编码 404 或 500 会降低代码可读性。这两个库提供了常量映射。

http-status-codes 提供了清晰的枚举对象,且对 TypeScript 支持友好。

// http-status-codes: 现代常量管理
const StatusCodes = require('http-status-codes');

// 获取状态码数字
console.log(StatusCodes.BAD_REQUEST); // 400

// 获取状态码文本
console.log(StatusCodes.getStatusText(404)); // 'Not Found'

http-status 提供了类似的常量,但导出结构略有不同,通常包含更多辅助方法。

// http-status: 传统常量管理
const status = require('http-status');

// 获取状态码数字
console.log(status.BAD_REQUEST); // 400

// 获取消息
console.log(status.getMessage(404)); // 'Not Found'

🧩 真实工程场景选型

场景 1:Express 全局错误处理中间件

你需要一个标准错误对象,能被 error-handling 中间件直接识别 err.status。

  • ✅ 最佳选择:http-errors
  • 理由:Express 生态事实标准,属性命名规范(status),无需额外适配。
// Express 中间件示例
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ message: err.message });
});

// 路由中抛出
throw createError(403, '无权访问');

场景 2:前端与后端共享状态码类型

你在 TypeScript 项目中,希望前端请求层和后端响应层使用同一套状态码定义。

  • ✅ 最佳选择:http-status-codes
  • 理由:类型定义完善,支持树摇(Tree-shaking),适合打包进前端应用。
// 共享类型文件
import { StatusCodes } from 'http-status-codes';

if (response.status === StatusCodes.UNAUTHORIZED) {
  redirectToLogin();
}

场景 3:维护旧的 hapi.js 系统

你正在维护一个基于 hapi v16 的遗留系统,代码中大量存在 Boom 调用。

  • ✅ 最佳选择:@hapi/boom (迁移目标)
  • 理由:原 boom 包已弃用,必须迁移到 scoped 包以保持安全性更新。
// 遗留代码迁移
// 旧:const Boom = require('boom');
// 新:const Boom = require('@hapi/boom');

return Boom.badImplementation('服务器内部错误');

场景 4:定义业务逻辑错误层级

你需要区分 DatabaseError、ValidationError 等业务错误,而不关心 HTTP 状态码。

  • ✅ 最佳选择:create-error
  • 理由:专注于 Error 子类化,不耦合 HTTP 协议细节。
const DatabaseError = createError('DatabaseError');
try {
  await db.query();
} catch (e) {
  throw new DatabaseError('连接失败', { cause: e });
}

📊 核心差异对比表

特性http-errorsboom (原)create-errorhttp-status-codeshttp-status
核心用途创建 HTTP 错误对象创建 hapi 错误对象创建错误构造函数状态码常量映射状态码常量映射
维护状态🟢 活跃🔴 已弃用🟡 低频🟢 活跃🟢 活跃
HTTP 耦合强 (自带 status)强 (自带 output)弱 (需手动附加)无 (仅常量)无 (仅常量)
TS 支持✅ 优秀✅ 良好⚠️ 一般✅ 优秀✅ 良好
适用框架Express/Koahapi (旧)通用全平台全平台

💡 架构师建议

在现代 Node.js 架构中,组合使用 是最佳实践。

  1. 错误处理:统一使用 http-errors。它是 Express 社区的标准,能够无缝集成到大多数中间件生态中。避免使用 boom,除非你被困在旧的 hapi 版本中。对于纯业务逻辑错误,create-error 可以作为补充,但在 Web 接口层最终应转换为 http-errors。

  2. 状态码管理:推荐 http-status-codes。它的命名空间更清晰,且在前后端同构项目中表现更好。避免在代码中直接写 404 或 500,使用 StatusCodes.NOT_FOUND 能显著提升代码自文档化能力。

  3. 避坑指南:

    • 不要混用错误库。如果团队选择了 http-errors,就不要在部分路由中使用 boom 风格,这会导致全局错误处理器逻辑复杂化。
    • 注意 boom 的弃用风险。扫描代码库中的 require('boom'),制定计划迁移至 @hapi/boom 或重构为 http-errors。

🏁 总结

http-errors 配合 http-status-codes 是目前 Node.js Web 开发中最稳健、最通用的组合。它们分工明确:一个负责抛出符合协议规范的错误对象,一个负责维护状态码的可读性。boom 属于特定历史时期的产物,create-error 则更偏向底层工具。理解它们的边界,能帮助你在构建高可维护性的后端服务时做出正确的依赖决策。

如何选择: boom vs create-error vs http-errors vs http-status vs http-status-codes

  • boom:

    仅在维护旧的 hapi.js 项目时考虑,官方已明确标记为弃用(Deprecated)。新项目应直接使用 @hapi/boom 或转向更通用的 http-errors。

  • create-error:

    适用于需要自定义错误层级结构但不需要 HTTP 状态码绑定的通用场景。如果是为了 Web 接口报错,优先选择 http-errors。

  • http-errors:

    Node.js 和 Express 开发的首选。如果你需要创建带有 status 属性的错误对象以便中间件统一处理,这是最标准、维护最活跃的选择。

  • http-status:

    提供状态码常量及辅助方法。如果你需要获取状态码对应的文本描述或类别判断,且项目已在使用该库,可继续保留。

  • http-status-codes:

    现代项目的首选状态码常量库。相比 http-status,它通常具有更好的 TypeScript 支持和更简洁的导出结构,适合前端与 Node 共享类型。

boom的README

boom Logo

HTTP-friendly error objects

Build Status Current Version

Lead Maintainer: Eran Hammer

Boom

boom provides a set of utilities for returning HTTP errors. Each utility returns a Boom error response object which includes the following properties:

  • isBoom - if true, indicates this is a Boom object instance. Note that this boolean should only be used if the error is an instance of Error. If it is not certain, use Boom.isBoom() instead.
  • isServer - convenience bool indicating status code >= 500.
  • message - the error message.
  • typeof - the constructor used to create the error (e.g. Boom.badRequest).
  • output - the formatted response. Can be directly manipulated after object construction to return a custom error response. Allowed root keys:
    • statusCode - the HTTP status code (typically 4xx or 5xx).
    • headers - an object containing any HTTP headers where each key is a header name and value is the header content.
    • payload - the formatted object used as the response payload (stringified). Can be directly manipulated but any changes will be lost if reformat() is called. Any content allowed and by default includes the following content:
      • statusCode - the HTTP status code, derived from error.output.statusCode.
      • error - the HTTP status message (e.g. 'Bad Request', 'Internal Server Error') derived from statusCode.
      • message - the error message derived from error.message.
  • inherited Error properties.

The Boom object also supports the following method:

reformat(debug)

Rebuilds error.output using the other object properties where:

  • debug - a Boolean that, when true, causes Internal Server Error messages to be left in tact. Defaults to false, meaning that Internal Server Error messages are redacted.

Note that Boom object will return true when used with instanceof Boom, but do not use the Boom prototype (they are either plain Error or the error prototype passed in). This means Boom objects should only be tested using instanceof Boom or Boom.isBoom() but not by looking at the prototype or contructor information. This limitation is to avoid manipulating the prototype chain which is very slow.

Helper Methods

new Boom(message, [options])

Creates a new Boom object using the provided message and then calling boomify() to decorate the error with the Boom properties, where:

  • message - the error message. If message is an error, it is the same as calling boomify() directly.
  • options - and optional object where:
    • statusCode - the HTTP status code. Defaults to 500 if no status code is already set.
    • data - additional error information (assigned to error.data).
    • decorate - an option with extra properties to set on the error object.
    • ctor - constructor reference used to crop the exception call stack output.
    • if message is an error object, also supports the other boomify() options.

boomify(err, [options])

Decorates an error with the Boom properties where:

  • err - the Error object to decorate.
  • options - optional object with the following optional settings:
    • statusCode - the HTTP status code. Defaults to 500 if no status code is already set and err is not a Boom object.
    • message - error message string. If the error already has a message, the provided message is added as a prefix. Defaults to no message.
    • decorate - an option with extra properties to set on the error object.
    • override - if false, the err provided is a Boom object, and a statusCode or message are provided, the values are ignored. Defaults to true (apply the provided statusCode and message options to the error regardless of its type, Error or Boom object).
var error = new Error('Unexpected input');
Boom.boomify(error, { statusCode: 400 });

isBoom(err)

Identifies whether an error is a Boom object. Same as calling instanceof Boom.

HTTP 4xx Errors

Boom.badRequest([message], [data])

Returns a 400 Bad Request error where:

  • message - optional message.
  • data - optional additional error data.
Boom.badRequest('invalid query');

Generates the following response payload:

{
    "statusCode": 400,
    "error": "Bad Request",
    "message": "invalid query"
}

Boom.unauthorized([message], [scheme], [attributes])

Returns a 401 Unauthorized error where:

  • message - optional message.
  • scheme can be one of the following:
    • an authentication scheme name
    • an array of string values. These values will be separated by ', ' and set to the 'WWW-Authenticate' header.
  • attributes - an object of values to use while setting the 'WWW-Authenticate' header. This value is only used when scheme is a string, otherwise it is ignored. Every key/value pair will be included in the 'WWW-Authenticate' in the format of 'key="value"' as well as in the response payload under the attributes key. Alternatively value can be a string which is use to set the value of the scheme, for example setting the token value for negotiate header. If string is used message parameter must be null. null and undefined will be replaced with an empty string. If attributes is set, message will be used as the 'error' segment of the 'WWW-Authenticate' header. If message is unset, the 'error' segment of the header will not be present and isMissing will be true on the error object.

If either scheme or attributes are set, the resultant Boom object will have the 'WWW-Authenticate' header set for the response.

Boom.unauthorized('invalid password');

Generates the following response:

"payload": {
    "statusCode": 401,
    "error": "Unauthorized",
    "message": "invalid password"
},
"headers" {}
Boom.unauthorized('invalid password', 'sample');

Generates the following response:

"payload": {
    "statusCode": 401,
    "error": "Unauthorized",
    "message": "invalid password",
    "attributes": {
        "error": "invalid password"
    }
},
"headers" {
  "WWW-Authenticate": "sample error=\"invalid password\""
}
Boom.unauthorized(null, 'Negotiate', 'VGhpcyBpcyBhIHRlc3QgdG9rZW4=');

Generates the following response:

"payload": {
    "statusCode": 401,
    "error": "Unauthorized",
    "attributes": "VGhpcyBpcyBhIHRlc3QgdG9rZW4="
},
"headers" {
  "WWW-Authenticate": "Negotiate VGhpcyBpcyBhIHRlc3QgdG9rZW4="
}
Boom.unauthorized('invalid password', 'sample', { ttl: 0, cache: null, foo: 'bar' });

Generates the following response:

"payload": {
    "statusCode": 401,
    "error": "Unauthorized",
    "message": "invalid password",
    "attributes": {
        "error": "invalid password",
        "ttl": 0,
        "cache": "",
        "foo": "bar"
    }
},
"headers" {
  "WWW-Authenticate": "sample ttl=\"0\", cache=\"\", foo=\"bar\", error=\"invalid password\""
}

Boom.paymentRequired([message], [data])

Returns a 402 Payment Required error where:

  • message - optional message.
  • data - optional additional error data.
Boom.paymentRequired('bandwidth used');

Generates the following response payload:

{
    "statusCode": 402,
    "error": "Payment Required",
    "message": "bandwidth used"
}

Boom.forbidden([message], [data])

Returns a 403 Forbidden error where:

  • message - optional message.
  • data - optional additional error data.
Boom.forbidden('try again some time');

Generates the following response payload:

{
    "statusCode": 403,
    "error": "Forbidden",
    "message": "try again some time"
}

Boom.notFound([message], [data])

Returns a 404 Not Found error where:

  • message - optional message.
  • data - optional additional error data.
Boom.notFound('missing');

Generates the following response payload:

{
    "statusCode": 404,
    "error": "Not Found",
    "message": "missing"
}

Boom.methodNotAllowed([message], [data], [allow])

Returns a 405 Method Not Allowed error where:

  • message - optional message.
  • data - optional additional error data.
  • allow - optional string or array of strings (to be combined and separated by ', ') which is set to the 'Allow' header.
Boom.methodNotAllowed('that method is not allowed');

Generates the following response payload:

{
    "statusCode": 405,
    "error": "Method Not Allowed",
    "message": "that method is not allowed"
}

Boom.notAcceptable([message], [data])

Returns a 406 Not Acceptable error where:

  • message - optional message.
  • data - optional additional error data.
Boom.notAcceptable('unacceptable');

Generates the following response payload:

{
    "statusCode": 406,
    "error": "Not Acceptable",
    "message": "unacceptable"
}

Boom.proxyAuthRequired([message], [data])

Returns a 407 Proxy Authentication Required error where:

  • message - optional message.
  • data - optional additional error data.
Boom.proxyAuthRequired('auth missing');

Generates the following response payload:

{
    "statusCode": 407,
    "error": "Proxy Authentication Required",
    "message": "auth missing"
}

Boom.clientTimeout([message], [data])

Returns a 408 Request Time-out error where:

  • message - optional message.
  • data - optional additional error data.
Boom.clientTimeout('timed out');

Generates the following response payload:

{
    "statusCode": 408,
    "error": "Request Time-out",
    "message": "timed out"
}

Boom.conflict([message], [data])

Returns a 409 Conflict error where:

  • message - optional message.
  • data - optional additional error data.
Boom.conflict('there was a conflict');

Generates the following response payload:

{
    "statusCode": 409,
    "error": "Conflict",
    "message": "there was a conflict"
}

Boom.resourceGone([message], [data])

Returns a 410 Gone error where:

  • message - optional message.
  • data - optional additional error data.
Boom.resourceGone('it is gone');

Generates the following response payload:

{
    "statusCode": 410,
    "error": "Gone",
    "message": "it is gone"
}

Boom.lengthRequired([message], [data])

Returns a 411 Length Required error where:

  • message - optional message.
  • data - optional additional error data.
Boom.lengthRequired('length needed');

Generates the following response payload:

{
    "statusCode": 411,
    "error": "Length Required",
    "message": "length needed"
}

Boom.preconditionFailed([message], [data])

Returns a 412 Precondition Failed error where:

  • message - optional message.
  • data - optional additional error data.
Boom.preconditionFailed();

Generates the following response payload:

{
    "statusCode": 412,
    "error": "Precondition Failed"
}

Boom.entityTooLarge([message], [data])

Returns a 413 Request Entity Too Large error where:

  • message - optional message.
  • data - optional additional error data.
Boom.entityTooLarge('too big');

Generates the following response payload:

{
    "statusCode": 413,
    "error": "Request Entity Too Large",
    "message": "too big"
}

Boom.uriTooLong([message], [data])

Returns a 414 Request-URI Too Large error where:

  • message - optional message.
  • data - optional additional error data.
Boom.uriTooLong('uri is too long');

Generates the following response payload:

{
    "statusCode": 414,
    "error": "Request-URI Too Large",
    "message": "uri is too long"
}

Boom.unsupportedMediaType([message], [data])

Returns a 415 Unsupported Media Type error where:

  • message - optional message.
  • data - optional additional error data.
Boom.unsupportedMediaType('that media is not supported');

Generates the following response payload:

{
    "statusCode": 415,
    "error": "Unsupported Media Type",
    "message": "that media is not supported"
}

Boom.rangeNotSatisfiable([message], [data])

Returns a 416 Requested Range Not Satisfiable error where:

  • message - optional message.
  • data - optional additional error data.
Boom.rangeNotSatisfiable();

Generates the following response payload:

{
    "statusCode": 416,
    "error": "Requested Range Not Satisfiable"
}

Boom.expectationFailed([message], [data])

Returns a 417 Expectation Failed error where:

  • message - optional message.
  • data - optional additional error data.
Boom.expectationFailed('expected this to work');

Generates the following response payload:

{
    "statusCode": 417,
    "error": "Expectation Failed",
    "message": "expected this to work"
}

Boom.teapot([message], [data])

Returns a 418 I'm a Teapot error where:

  • message - optional message.
  • data - optional additional error data.
Boom.teapot('sorry, no coffee...');

Generates the following response payload:

{
    "statusCode": 418,
    "error": "I'm a Teapot",
    "message": "Sorry, no coffee..."
}

Boom.badData([message], [data])

Returns a 422 Unprocessable Entity error where:

  • message - optional message.
  • data - optional additional error data.
Boom.badData('your data is bad and you should feel bad');

Generates the following response payload:

{
    "statusCode": 422,
    "error": "Unprocessable Entity",
    "message": "your data is bad and you should feel bad"
}

Boom.locked([message], [data])

Returns a 423 Locked error where:

  • message - optional message.
  • data - optional additional error data.
Boom.locked('this resource has been locked');

Generates the following response payload:

{
    "statusCode": 423,
    "error": "Locked",
    "message": "this resource has been locked"
}

Boom.failedDependency([message], [data])

Returns a 424 Failed Dependency error where:

  • message - optional message.
  • data - optional additional error data.
Boom.failedDependency('an external resource failed');

Generates the following response payload:

{
    "statusCode": 424,
    "error": "Failed Dependency",
    "message": "an external resource failed"
}

Boom.preconditionRequired([message], [data])

Returns a 428 Precondition Required error where:

  • message - optional message.
  • data - optional additional error data.
Boom.preconditionRequired('you must supply an If-Match header');

Generates the following response payload:

{
    "statusCode": 428,
    "error": "Precondition Required",
    "message": "you must supply an If-Match header"
}

Boom.tooManyRequests([message], [data])

Returns a 429 Too Many Requests error where:

  • message - optional message.
  • data - optional additional error data.
Boom.tooManyRequests('you have exceeded your request limit');

Generates the following response payload:

{
    "statusCode": 429,
    "error": "Too Many Requests",
    "message": "you have exceeded your request limit"
}

Boom.illegal([message], [data])

Returns a 451 Unavailable For Legal Reasons error where:

  • message - optional message.
  • data - optional additional error data.
Boom.illegal('you are not permitted to view this resource for legal reasons');

Generates the following response payload:

{
    "statusCode": 451,
    "error": "Unavailable For Legal Reasons",
    "message": "you are not permitted to view this resource for legal reasons"
}

HTTP 5xx Errors

All 500 errors hide your message from the end user. Your message is recorded in the server log.

Boom.badImplementation([message], [data]) - (alias: internal)

Returns a 500 Internal Server Error error where:

  • message - optional message.
  • data - optional additional error data.
Boom.badImplementation('terrible implementation');

Generates the following response payload:

{
    "statusCode": 500,
    "error": "Internal Server Error",
    "message": "An internal server error occurred"
}

Boom.notImplemented([message], [data])

Returns a 501 Not Implemented error where:

  • message - optional message.
  • data - optional additional error data.
Boom.notImplemented('method not implemented');

Generates the following response payload:

{
    "statusCode": 501,
    "error": "Not Implemented",
    "message": "method not implemented"
}

Boom.badGateway([message], [data])

Returns a 502 Bad Gateway error where:

  • message - optional message.
  • data - optional additional error data.
Boom.badGateway('that is a bad gateway');

Generates the following response payload:

{
    "statusCode": 502,
    "error": "Bad Gateway",
    "message": "that is a bad gateway"
}

Boom.serverUnavailable([message], [data])

Returns a 503 Service Unavailable error where:

  • message - optional message.
  • data - optional additional error data.
Boom.serverUnavailable('unavailable');

Generates the following response payload:

{
    "statusCode": 503,
    "error": "Service Unavailable",
    "message": "unavailable"
}

Boom.gatewayTimeout([message], [data])

Returns a 504 Gateway Time-out error where:

  • message - optional message.
  • data - optional additional error data.
Boom.gatewayTimeout();

Generates the following response payload:

{
    "statusCode": 504,
    "error": "Gateway Time-out"
}

F.A.Q.

Q How do I include extra information in my responses? output.payload is missing data, what gives?

A There is a reason the values passed back in the response payloads are pretty locked down. It's mostly for security and to not leak any important information back to the client. This means you will need to put in a little more effort to include extra information about your custom error. Check out the "Error transformation" section in the hapi documentation.