In this lesson, you will learn how to map internal application error hierarchies to semantically correct HTTP status codes to ensure your clients receive consistent, actionable feedback.
When building resilient Node.js services, it is tempting to use generic 500 Internal Server Error responses for everything. However, to support client-side resilience, your API must distinguish between errors that are the client's fault, the server's fault, or a temporary state that warrants a retry.
Establishing the Hierarchy
Instead of passing raw Error objects to your response handler, define an internal error hierarchy that maps specific exception types to HTTP statuses:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
// Map domain errors to HTTP statuses
const errorMap = {
ValidationError: 400, // Bad Request
UnauthorizedError: 401, // Authentication needed
ForbiddenError: 403, // Known identity, insufficient permissions
NotFoundError: 404, // Resource missing
ConflictError: 409, // Idempotency key mismatch or state conflict
RateLimitError: 429, // Too many requests
ServiceUnavailable: 503 // Transient overload or maintenance
};
Why Semantics Matter
By standardizing these codes, you enable the client to make intelligent decisions without needing to parse your response bodies. For example, a 429 Too Many Requests code allows the client to honor the Retry-After header automatically, whereas a 400 Bad Request signals the client that they should never retry that specific request without changing the payload.
When an error propagates up your middleware stack, catch it and apply the mapping. If an error is unknown, default to 500. This creates a predictable contract for your consumers:
app.use((err, req, res, next) => {
const statusCode = errorMap[err.constructor.name] || 500;
res.status(statusCode).json({
error: err.message,
code: err.constructor.name
});
});
Using standard status codes is the foundation of a "self-describing" API. It prevents the client from guessing why a request failed and allows you to build sophisticated automated recovery mechanisms into your service consumption layers.
Key Takeaway: Map internal error types to precise HTTP status codes to enable client-side resilience and ensure your API communicates intent rather than just reporting failure.