Appearance
HTTP Error Classes
A class hierarchy for HTTP errors with automatic status codes, default messages from Node's STATUS_CODES, and support for attaching extra details. Throw structured errors that Express error handlers can inspect by status.
When to Use
- Throwing errors that map to specific HTTP status codes
- Building Express error-handling middleware that switches on
error.status - Attaching structured details (field names, limits, context) to errors
- Distinguishing client errors (4xx) from server errors (5xx) in catch blocks
The Pattern
javascript
import { STATUS_CODES } from "node:http";
class HttpError extends Error {
static status = 500;
status = 500;
constructor(message, extras = {}) {
super(message);
Object.assign(this, extras);
if (!this.message) {
this.message = STATUS_CODES[this.status];
}
}
}
class ClientError extends HttpError {
static status = 400;
status = 400;
}
class UnauthorizedError extends ClientError {
static status = 401;
status = 401;
}
class ForbiddenError extends ClientError {
static status = 403;
status = 403;
}
class NotFoundError extends ClientError {
static status = 404;
status = 404;
}
class ServerError extends HttpError {
static status = 500;
status = 500;
}Hierarchy
HttpError (500)
├── ClientError (400)
│ ├── UnauthorizedError (401)
│ ├── ForbiddenError (403)
│ └── NotFoundError (404)
└── ServerError (500)Design decisions
- Both static and instance
status. The static property enablesUnauthorizedError.status === 401checks without instantiation. The instance property enableserror.statuson thrown instances. STATUS_CODESfallback. If no message is passed, the error uses Node's built-in status text ("Not Found", "Forbidden", etc.).extrasobject. Additional properties are merged onto the error instance, so handlers can accesserror.field,error.limit, etc.
Throwing errors
Throw with no message to use Node's built-in status text as the default:
javascript
throw new NotFoundError();
// error.message === "Not Found"
// error.status === 404Pass a custom message to override the default:
javascript
throw new UnauthorizedError("Invalid token");
// error.message === "Invalid token"
// error.status === 401Attach structured details through the extras parameter for handlers to inspect:
javascript
throw new ClientError("Validation failed", {
field: "email",
reason: "Invalid format",
});
// error.field === "email"
// error.reason === "Invalid format"
// error.status === 400Express error handler
An Express error-handling middleware can branch on instanceof to separate client errors from server errors:
javascript
app.use((error, req, res, next) => {
if (error instanceof ClientError) {
res.status(error.status).json({
error: error.message,
...error,
});
} else {
res.status(500).json({
error: "Internal Server Error",
});
}
});Checking error category
Use instanceof with parent classes to catch broad categories without listing individual status codes:
javascript
try {
await fetchResource(id);
} catch (error) {
if (error instanceof ClientError) {
// 4xx: client's fault
} else if (error instanceof ServerError) {
// 5xx: our fault
}
}Trade-offs
| Approach | Pros | Cons |
|---|---|---|
| Class hierarchy | Type-safe, instanceof | More classes |
| Plain objects | Simple | No stack trace |
| String codes | Flat, serializable | No hierarchy |
The hierarchy is the key value. Catching ClientError catches all 4xx errors. Catching HttpError catches everything. This lets error handlers be as broad or narrow as needed without listing individual status codes.