API Errors Explained Like You’re Talking to a Friend
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
| Range | Meaning |
| 1xx | Informational |
| 2xx | Success |
| 3xx | Redirection |
| 4xx | Client Errors |
| 5xx | Server 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.