A comprehensive and type-safe HTTP error handling library for Node.js and TypeScript applications.
forge-error
provides a structured, standardized, and consistent approach to defining, throwing, and handling HTTP errors with minimal boilerplate code. With fully typed error classes and built-in support for standardized error responses, it helps you write cleaner, more maintainable error handling logic in your application.
npm install forge-error
# or
yarn add forge-error
# or
pnpm add forge-error
- Full suite of HTTP error classes (4xx and 5xx)
- Standardized error payload structure
- First-class TypeScript support
- Custom error code and data support
- Utility functions to handle, format, and inspect errors
- Seamless integration with Express.js
- Async controller wrapper for concise error handling
You can create errors using one of the built-in error classes or create fully custom errors with ForgeError
.
import { NotFoundError } from 'forge-error';
throw new NotFoundError('Resource not found');
import { BadRequestError } from 'forge-error';
throw new BadRequestError({
message: 'Invalid input data',
data: {
field: 'email',
issue: 'Invalid format'
},
errorCode: 'INVALID_EMAIL'
});
import { ForgeError } from 'forge-error';
throw new ForgeError(
'Custom error occurred',
418,
'CUSTOM_ERROR_CODE',
{ additional: 'context' }
);
You can easily integrate forge-error
with your Express.js application to return consistent error responses.
Use handleAsyncRequest
to wrap controller logic and eliminate manual try-catch
blocks:
import express, { Request, Response } from 'express';
import { handleAsyncRequest } from 'forge-error';
const app = express();
app.post('/login', handleAsyncRequest(async (req: Request, res: Response) => {
// Your logic here
res.status(200).json({ message: 'Login successful' });
}));
You can also export wrapped handlers:
// controllers/auth.controller.ts
app.post('/login', loginController);
const loginController = handleAsyncRequest(async (req, res) => {
// Logic here
});
Use getForgeErrorPayload
to build standardized error responses in your global error middleware:
const app = express();
app.use(routes)
app.use((error, req, res, next) => {
const { statusCode, errorCode, message, data } = getForgeErrorPayload(error);
res.status(statusCode).json({
success: false,
statusCode,
errorCode,
message,
...(data ? { data } : {})
});
});
app.use((req, res) => {
res.status(404).json({
success: false,
statusCode: 404,
errorCode: 'NOT_FOUND',
message: 'Resource not found'
});
})
import { ErrorRequestHandler } from 'express';
import { getForgeErrorPayload } from 'forge-error';
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
const { statusCode, errorCode, message, data } = getForgeErrorPayload(err);
res.status(statusCode).json({
success: false,
statusCode,
errorCode,
message,
...(data ? { data } : {})
});
};
export { errorHandler };
The library provides predefined error classes corresponding to standard HTTP status codes.
import {
BadRequestError, // 400
UnauthorizedError, // 401
ForbiddenError, // 403
NotFoundError, // 404
MethodNotAllowedError, // 405
NotAcceptableError, // 406
ConflictError, // 409
// ... and more
} from 'forge-error';
import {
InternalServerError, // 500
NotImplementedError, // 501
BadGatewayError, // 502
ServiceUnavailableError, // 503
GatewayTimeoutError, // 504
// ... and more
} from 'forge-error';
The package provides several utility functions to handle error payloads. These are divided into two categories:
import { getErrorPayload, getClientErrorPayload, getServerErrorPayload } from 'forge-error';
// 1. Get payload for any forge error
const error1 = new NotFoundError('Resource not found');
const payload1 = getErrorPayload(error1);
// Returns: { message: 'Resource not found', statusCode: 404, errorCode: 'NOT_FOUND' }
const error2 = new Error('Regular error');
const payload2 = getErrorPayload(error2);
// Returns: undefined
// 2. Get payload only for client errors (4xx)
const clientError = new NotFoundError('Not found');
const clientPayload = getClientErrorPayload(clientError);
// Returns: { message: 'Not found', statusCode: 404, errorCode: 'NOT_FOUND' }
const serverError = new InternalServerError('Server error');
const wrongPayload = getClientErrorPayload(serverError);
// Returns: undefined
// 3. Get payload only for server errors (5xx)
const serverPayload = getServerErrorPayload(serverError);
// Returns: { message: 'Server error', statusCode: 500, errorCode: 'INTERNAL_SERVER_ERROR' }
import {
getForgeClientErrorPayload,
getForgeServerErrorPayload,
getForgeErrorPayload
} from 'forge-error';
// 1. Always get a client error payload
const error = new Error('Unknown error');
const clientPayload = getForgeClientErrorPayload(error);
// Returns: {
// message: 'Unknown error',
// statusCode: 400, // Default client error status
// errorCode: 'BAD_REQUEST' // Default client error code
// }
// 2. Always get a server error payload
const serverPayload = getForgeServerErrorPayload(error);
// Returns: {
// message: 'Unknown error',
// statusCode: 500, // Default server error status
// errorCode: 'INTERNAL_SERVER_ERROR' // Default server error code
// }
// 3. Always get a generic error payload (uses server error defaults)
const genericPayload = getForgeErrorPayload(error);
// Returns: {
// message: 'Unknown error',
// statusCode: 500,
// errorCode: 'INTERNAL_SERVER_ERROR'
// }
// Common usage in Express error handler:
app.use((error, req, res, next) => {
// Will always return a valid error response
const { statusCode, ...payload } = getForgeErrorPayload(error);
res.status(statusCode).json(payload);
});
import { isClientError, isServerError, isBaseError } from 'forge-error';
const error = new NotFoundError('Resource not found');
isClientError(error); // true
isServerError(error); // false
isBaseError(error); // true
All forge errors return a standardized structure:
{
message: string; // Human-readable error message
statusCode: number; // HTTP status code
errorCode: string; // Error identifier
data?: object; // Optional additional context
}
This format is useful for consistent API responses, logging, and debugging.
try {
// Your code here
throw new BadRequestError('Invalid input');
} catch (error) {
if (error instanceof BadRequestError) {
const payload = error.buildErrorPayload();
// Handle the error with standardized payload
console.log(payload);
/*
{
message: 'Invalid input',
statusCode: 400,
errorCode: 'BAD_REQUEST'
}
*/
}
}
The library includes a comprehensive set of predefined HTTP error types with their default values. You can override these defaults using a detailed object.
Error Class | Status Code | Error Code | Default Message |
---|---|---|---|
4xx Client Errors | |||
BadRequestError | 400 | BAD_REQUEST | The request could not be processed due to invalid syntax or parameters |
UnauthorizedError | 401 | UNAUTHORIZED | Authentication is required to access this resource |
PaymentRequiredError | 402 | PAYMENT_REQUIRED | Payment is required to access this resource |
ForbiddenError | 403 | FORBIDDEN | You do not have permission to access this resource |
NotFoundError | 404 | NOT_FOUND | The requested resource could not be found |
MethodNotAllowedError | 405 | METHOD_NOT_ALLOWED | The requested HTTP method is not allowed for this resource |
NotAcceptableError | 406 | NOT_ACCEPTABLE | The requested content type cannot be served by this resource |
ProxyAuthenticationRequiredError | 407 | PROXY_AUTHENTICATION_REQUIRED | Proxy server authentication is required |
RequestTimeoutError | 408 | REQUEST_TIMEOUT | The server timed out waiting for the request |
ConflictError | 409 | CONFLICT | The request conflicts with the current state |
GoneError | 410 | GONE | The requested resource is no longer available |
LengthRequiredError | 411 | LENGTH_REQUIRED | Content-Length header is required |
PreconditionFailedError | 412 | PRECONDITION_FAILED | One or more conditions failed |
PayloadTooLargeError | 413 | PAYLOAD_TOO_LARGE | Request payload exceeds the limit |
URITooLongError | 414 | URI_TOO_LONG | Request URI exceeds the maximum length |
UnsupportedMediaTypeError | 415 | UNSUPPORTED_MEDIA_TYPE | The content type is not supported |
RangeNotSatisfiableError | 416 | RANGE_NOT_SATISFIABLE | The requested range cannot be satisfied |
ExpectationFailedError | 417 | EXPECTATION_FAILED | Server cannot meet the requirements |
TeapotError | 418 | IM_A_TEAPOT | I'm a teapot |
SessionExpiredError | 419 | SESSION_EXPIRED | Your session has expired. Please refresh and try again |
RateLimitError | 420 | RATE_LIMIT_EXCEEDED | Request rate limit has been exceeded |
MisdirectedRequestError | 421 | MISDIRECTED_REQUEST | The request was sent to a server unable to produce a response |
UnprocessableEntityError | 422 | UNPROCESSABLE_ENTITY | The request was well-formed but invalid |
LockedError | 423 | RESOURCE_LOCKED | The requested resource is currently locked |
FailedDependencyError | 424 | FAILED_DEPENDENCY | The request failed due to the failure of a previous request |
TooEarlyError | 425 | TOO_EARLY | The server is not ready to process the request |
UpgradeRequiredError | 426 | UPGRADE_REQUIRED | Client must upgrade to a different protocol version |
PreconditionRequiredError | 428 | PRECONDITION_REQUIRED | This request requires preconditions to be specified |
TooManyRequestsError | 429 | TOO_MANY_REQUESTS | Rate limit exceeded |
RequestHeaderFieldsTooLargeError | 431 | REQUEST_HEADERS_TOO_LARGE | Request header fields exceed maximum size |
NoResponseError | 444 | NO_RESPONSE | Server returned no information and closed the connection |
RetryWithError | 449 | RETRY_WITH | The request should be retried after performing the appropriate action |
BlockedByWindowsParentalControlsError | 450 | BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS | Access denied by Windows parental controls |
LegallyRestrictedError | 451 | LEGALLY_RESTRICTED | Access denied for legal reasons |
RequestHeaderTooLargeError | 494 | REQUEST_HEADER_TOO_LARGE | Request header is too large |
SSLCertificateError | 495 | SSL_CERTIFICATE_ERROR | SSL/TLS certificate validation error |
SSLCertificateRequiredError | 496 | SSL_CERTIFICATE_REQUIRED | A valid SSL/TLS certificate is required |
HTTPRequestSentToHTTPSPortError | 497 | HTTP_REQUEST_SENT_TO_HTTPS_PORT | An HTTP request was sent to an HTTPS port |
InvalidTokenError | 498 | INVALID_TOKEN | The token provided is invalid or has expired |
TokenRequiredError | 499 | TOKEN_REQUIRED | A valid token is required to access this resource |
ClientClosedRequestError | 499 | CLIENT_CLOSED_REQUEST | Client closed the request before the server could respond |
5xx Server Errors | |||
InternalServerError | 500 | INTERNAL_SERVER_ERROR | An unexpected error occurred |
NotImplementedError | 501 | NOT_IMPLEMENTED | Not implemented |
BadGatewayError | 502 | BAD_GATEWAY | Invalid response from upstream server |
ServiceUnavailableError | 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable |
GatewayTimeoutError | 504 | GATEWAY_TIMEOUT | Gateway timeout |
HTTPVersionNotSupportedError | 505 | HTTP_VERSION_NOT_SUPPORTED | HTTP version not supported |
VariantAlsoNegotiatesError | 506 | VARIANT_ALSO_NEGOTIATES | Server configuration error in content negotiation |
InsufficientStorageError | 507 | INSUFFICIENT_STORAGE | Insufficient storage space to complete the request |
LoopDetectedError | 508 | LOOP_DETECTED | Request processing stopped due to infinite loop detection |
BandwidthLimitExceededError | 509 | BANDWIDTH_LIMIT_EXCEEDED | Server bandwidth limit has been exceeded |
NotExtendedError | 510 | NOT_EXTENDED | Server requires additional extensions to fulfill the request |
NetworkAuthenticationRequiredError | 511 | NETWORK_AUTHENTICATION_REQUIRED | Network authentication is required |
Contributions are welcome! Please feel free to submit a Pull Request.