API Error Handling
A lightweight, production-ready error handling utility for Express applications.
This component provides:
- Standardized HTTP status codes
- A typed ApiError class
- Consistent, predictable error responses
- Clear separation between operational and programmer errors
Installation Guide
This component requires additional ServerCN components.
👉 Note: You do not need to install any servercn dependencies manually. Installing this component will automatically install all required servercn dependencies. Manual installation is optional if you prefer to manage dependencies yourself.
1. Install ServerCN dependencies(Optional)
npx servercn add http-status-codesDocumentation: HTTP Status Codes
2. Install this component
npx servercn add error-handler⚠️ If this dependency is not installed, the component will not function correctly.
Basic Implementation
1. MVC Structure
src/utils/api-error.ts
import { STATUS_CODES, StatusCode } from "../constants/status-codes";
export class ApiError extends Error {
public readonly statusCode: StatusCode;
public readonly isOperational: boolean;
public readonly errors?: unknown;
constructor(
statusCode: StatusCode,
message: string,
errors?: unknown,
isOperational = true
) {
super(message);
this.name = "ApiError";
this.statusCode = statusCode;
this.errors = errors;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
static badRequest(message = "Bad Request", errors?: unknown) {
return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);
}
static unauthorized(message = "Unauthorized") {
return new ApiError(STATUS_CODES.UNAUTHORIZED, message);
}
static forbidden(message = "Forbidden") {
return new ApiError(STATUS_CODES.FORBIDDEN, message);
}
static notFound(message = "Not Found") {
return new ApiError(STATUS_CODES.NOT_FOUND, message);
}
static conflict(message = "Conflict") {
return new ApiError(STATUS_CODES.CONFLICT, message);
}
static validation(message = "Validation failed", errors?: unknown) {
return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);
}
static notImplemented(message = "Not Implemented") {
return new ApiError(STATUS_CODES.NOT_IMPLEMENTED, message);
}
static badGateway(message = "Bad Gateway") {
return new ApiError(STATUS_CODES.BAD_GATEWAY, message);
}
static serviceUnavailable(message = "Service Unavailable") {
return new ApiError(STATUS_CODES.SERVICE_UNAVAILABLE, message);
}
static tooManyRequests(message = "Too Many Requests") {
return new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, message);
}
static server(message = "Internal Server Error") {
return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);
}
}
/*
* Usage:
* throw new ApiError(STATUS_CODES.NOT_FOUND, "Not found");
* throw ApiError.badRequest("Bad request");
*/2. Feature Structure
src/shared/errors/api-error.ts
import { STATUS_CODES, StatusCode } from "../constants/status-codes";
export class ApiError extends Error {
public readonly statusCode: StatusCode;
public readonly isOperational: boolean;
public readonly errors?: unknown;
constructor(
statusCode: StatusCode,
message: string,
errors?: unknown,
isOperational = true
) {
super(message);
this.name = "ApiError";
this.statusCode = statusCode;
this.errors = errors;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
static badRequest(message = "Bad Request", errors?: unknown) {
return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);
}
static unauthorized(message = "Unauthorized") {
return new ApiError(STATUS_CODES.UNAUTHORIZED, message);
}
static forbidden(message = "Forbidden") {
return new ApiError(STATUS_CODES.FORBIDDEN, message);
}
static notFound(message = "Not Found") {
return new ApiError(STATUS_CODES.NOT_FOUND, message);
}
static conflict(message = "Conflict") {
return new ApiError(STATUS_CODES.CONFLICT, message);
}
static validation(message = "Validation failed", errors?: unknown) {
return new ApiError(STATUS_CODES.BAD_REQUEST, message, errors);
}
static notImplemented(message = "Not Implemented") {
return new ApiError(STATUS_CODES.NOT_IMPLEMENTED, message);
}
static badGateway(message = "Bad Gateway") {
return new ApiError(STATUS_CODES.BAD_GATEWAY, message);
}
static serviceUnavailable(message = "Service Unavailable") {
return new ApiError(STATUS_CODES.SERVICE_UNAVAILABLE, message);
}
static tooManyRequests(message = "Too Many Requests") {
return new ApiError(STATUS_CODES.TOO_MANY_REQUESTS, message);
}
static server(message = "Internal Server Error") {
return new ApiError(STATUS_CODES.INTERNAL_SERVER_ERROR, message);
}
}
/*
* Usage:
* throw new ApiError(STATUS_CODES.NOT_FOUND, "Not found");
* throw ApiError.badRequest("Bad request");
*/Usage Example
import { ApiError } from "../utils/api-error";
import { AsyncHandler } from "../utils/async-handler";
export const signinUser = AsyncHandler(async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
throw ApiError.badRequest("Missing email or password");
}
// ...
});Error Response
If email or password is missing, then return standardized responses:
{
"success": false,
"message": "Missing email or password",
"statusCode": 400
}