Skip to main content

Command Palette

Search for a command to run...

API Errors Explained Like You’re Talking to a Friend

Updated
6 min read
R

Full Stack Engineer specializing in the JavaScript Ecosystem (Next.js, Node.js, TypeScript). Expert in building scalable, production-grade web platforms (e.g., ChromaDec) with a focus on high-performance architecture. Additionally skilled in Enterprise Microservices (Java/Quarkus) and Cross-Platform Mobile development, bringing strict backend discipline to the modern web stack.

If you’ve ever built an API, you’ve definitely seen numbers like 400, 404, or 500 pop up.

At first, they look scary. But once you understand them, they actually make your API cleaner, smarter, and more professional.

Let’s break them down in a simple way.


What are API errors, really?

API errors are based on HTTP status codes.

Think of them as messages your server sends back to the client saying:

“Hey, something happened. Here’s what it means.”

These codes are grouped into categories


HTTP Status Code Categories

RangeMeaning
1xxInformational
2xxSuccess
3xxRedirection
4xxClient Errors
5xxServer Errors

Most backend developers deal with 4xx and 5xx errors.

That’s where your ApiError class usually comes in.


4xx — Client Errors (User Mistakes)

These errors mean: “The client did something wrong.”

400 — Bad Request

Wrong input or invalid data.

ApiError.badRequest("Invalid data");

Examples:

  • Missing fields

  • Wrong format

  • Invalid values


401 — Unauthorized

User is not logged in or token is invalid.

ApiError.unauthorized();

Think: “Who are you?”


403 — Forbidden

User is logged in but not allowed.

ApiError.forbidden();

Difference from 401:

  • 401 → not logged in

  • 403 → logged in but no permission


404 — Not Found

Requested resource doesn’t exist.

ApiError.notFound("User not found");

Classic example: wrong URL or missing data.


405 — Method Not Allowed

Wrong HTTP method.

new ApiError(405, "Method not allowed");

Example:

  • Sending GET instead of POST.

409 — Conflict

Duplicate or conflicting data.

ApiError.conflict("Email already exists");

Very common in signup APIs.


422 — Unprocessable Entity

Validation error.

new ApiError(422, "Validation failed");

Many developers prefer 422 instead of 400 for validation.


429 — Too Many Requests

Rate limit exceeded.

new ApiError(429, "Too many requests");

Think: “Slow down, bro ”


5xx — Server Errors (Backend Problems)

These mean: “The server messed up.”

500 — Internal Server Error

Unknown server problem.

ApiError.internal();

Could be:

  • Bug in code

  • Database crash

  • Unexpected error


502 — Bad Gateway

Server got a bad response from another server.

new ApiError(502, "Bad gateway");

503 — Service Unavailable

Server is overloaded or down.

new ApiError(503, "Service unavailable");

504 — Gateway Timeout

Another server didn’t respond in time.

new ApiError(504, "Gateway timeout");

The Errors You’ll Use Most in Real Projects

In real-world APIs, these cover almost everything:

400, 401, 403, 404, 409, 422, 429 , 500, 503

Together, they cover about 90% of API error cases.


Why use ApiError instead of normal Error?

Normal error:

throw new Error("Something went wrong");

ApiError:

throw ApiError.badRequest("Invalid input");

With ApiError, you get:

  • statusCode

  • message

  • isOperational

  • details

  • timestamp

That’s gold for debugging and clean API responses.

class ApiError extends Error {
  constructor(statusCode, message, isOperational = true, details = null) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    this.isOperational = isOperational;
    this.details = details;
    this.timestamp = new Date().toISOString();
    Error.captureStackTrace(this, this.constructor);
  }

  // 400 - Bad Request
  static badRequest(message = "Bad request", details = null) {
    return new ApiError(400, message, true, details);
  }

  // 401 - Unauthorized
  static unauthorized(message = "Unauthorized", details = null) {
    return new ApiError(401, message, true, details);
  }

  // 403 - Forbidden
  static forbidden(message = "Forbidden", details = null) {
    return new ApiError(403, message, true, details);
  }

  // 404 - Not Found
  static notFound(message = "Resource not found", details = null) {
    return new ApiError(404, message, true, details);
  }

  // 405 - Method Not Allowed
  static methodNotAllowed(message = "Method not allowed", details = null) {
    return new ApiError(405, message, true, details);
  }

  // 408 - Request Timeout
  static requestTimeout(message = "Request timeout", details = null) {
    return new ApiError(408, message, true, details);
  }

  // 409 - Conflict
  static conflict(message = "Conflict", details = null) {
    return new ApiError(409, message, true, details);
  }

  // 410 - Gone
  static gone(message = "Resource no longer available", details = null) {
    return new ApiError(410, message, true, details);
  }

  // 413 - Payload Too Large
  static payloadTooLarge(message = "Payload too large", details = null) {
    return new ApiError(413, message, true, details);
  }

  // 415 - Unsupported Media Type
  static unsupportedMediaType(message = "Unsupported media type", details = null) {
    return new ApiError(415, message, true, details);
  }

  // 422 - Unprocessable Entity (Validation Error)
  static validationError(message = "Validation failed", details = null) {
    return new ApiError(422, message, true, details);
  }

  // 429 - Too Many Requests
  static tooManyRequests(message = "Too many requests", details = null) {
    return new ApiError(429, message, true, details);
  }

  // 500 - Internal Server Error
  static internal(message = "Internal server error", details = null) {
    return new ApiError(500, message, false, details);
  }

  // 502 - Bad Gateway
  static badGateway(message = "Bad gateway", details = null) {
    return new ApiError(502, message, false, details);
  }

  // 503 - Service Unavailable
  static serviceUnavailable(message = "Service unavailable", details = null) {
    return new ApiError(503, message, false, details);
  }

  // 504 - Gateway Timeout
  static gatewayTimeout(message = "Gateway timeout", details = null) {
    return new ApiError(504, message, false, details);
  }
}

module.exports = ApiError;

What you achieved with this class

Now your API supports:

Client Errors (4xx)

  • badRequest (400)

  • unauthorized (401)

  • forbidden (403)

  • notFound (404)

  • methodNotAllowed (405)

  • requestTimeout (408)

  • conflict (409)

  • gone (410)

  • payloadTooLarge (413)

  • unsupportedMediaType (415)

  • validationError (422)

  • tooManyRequests (429)

Server Errors (5xx)

  • internal (500)

  • badGateway (502)

  • serviceUnavailable (503)

  • gatewayTimeout (504)


Example usage

throw ApiError.validationError("Email is invalid", { field: "email" });
throw ApiError.tooManyRequests();
throw ApiError.internal("Database connection failed");

Uncaught Exception (Unhandled Error)

A synchronous error that was never caught with try...catch.

Example:

function test() {
  console.log(a); // ❌ a is not defined
}

test();

This throws an error immediately.

Since there is no try...catch, Node.js triggers:

uncaughtException


Unhandled Promise Rejection

An asynchronous error (Promise/async) that was never handled with .catch() or try...catch.

Example:

Promise.reject("Something went wrong");

or

async function test() {
  throw new Error("Async error");
}

test(); //  no catch

Since the promise rejection is not handled, Node.js triggers:

Final Thought

Once you understand API errors, you stop seeing them as random numbers.

Instead, you start using them like a language.

A language that tells your users, your frontend, and your future self exactly what went wrong.

And that’s when your backend starts to feel… professional.