Course contentsShow
← Node.js
Lesson 1458 of 1,98013. Error Handling and Resilient SystemsFree lesson

Standardizing Error HTTP Status Codes

You will map internal error hierarchies to semantically correct HTTP response status codes for client-side consumption.

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.