@nextrush/errors
Standardized HTTP error classes with proper status codes
HTTP error classes for consistent error handling across your NextRush application.
$ pnpm add @nextrush/errors
Usually Included
Most users install nextrush which includes common error classes. Install @nextrush/errors
directly for the full error hierarchy.
What It Provides
// Base classes
import { HttpError, NextRushError, getHttpStatusMessage } from '@nextrush/errors';
// 4xx Client Errors
import {
BadRequestError, // 400
UnauthorizedError, // 401
ForbiddenError, // 403
NotFoundError, // 404
MethodNotAllowedError, // 405
ConflictError, // 409
UnprocessableEntityError, // 422
TooManyRequestsError, // 429
// ... and more
} from '@nextrush/errors';
// 5xx Server Errors
import {
InternalServerError, // 500
NotImplementedError, // 501
BadGatewayError, // 502
ServiceUnavailableError, // 503
GatewayTimeoutError, // 504
// ... and more
} from '@nextrush/errors';
// Validation errors
import {
ValidationError,
RequiredFieldError,
TypeMismatchError,
RangeValidationError,
LengthError,
PatternError,
InvalidEmailError,
InvalidUrlError,
} from '@nextrush/errors';
// Factory functions
import {
createError,
badRequest,
unauthorized,
forbidden,
notFound,
// ... and more
} from '@nextrush/errors';
// Middleware
import { errorHandler, notFoundHandler, catchAsync } from '@nextrush/errors';Basic Usage
import { NotFoundError, BadRequestError } from '@nextrush/errors';
router.get('/users/:id', async (ctx) => {
const user = await db.users.findById(ctx.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
ctx.json(user);
});
router.post('/users', async (ctx) => {
const { email } = ctx.body as { email?: string };
if (!email) {
throw new BadRequestError('Email is required');
}
// ...
});Error Classes
Error Class Hierarchy
All NextRush errors extend NextRushError. All HTTP errors extend HttpError:
class NextRushError extends Error {
readonly status: number;
readonly code: string;
readonly expose: boolean;
readonly details?: Record<string, unknown>;
readonly cause?: unknown;
}
class HttpError extends NextRushError {
constructor(status: number, message?: string, options?: HttpErrorOptions);
}NextRushError Properties
| Property | Type | Description |
|---|---|---|
status | number= 500 | HTTP status code |
code | string= 'INTERNAL_ERROR' | Machine-readable error code |
expose | boolean= true for 4xx, false for 5xx | Whether to expose message to client |
details? | Record<string, unknown> | Additional structured data |
cause? | unknown | Original error for error chaining |
4xx Client Errors
| Class | Status | Default Code | Use Case |
|---|---|---|---|
BadRequestError | 400 | BAD_REQUEST | Invalid input |
UnauthorizedError | 401 | UNAUTHORIZED | Auth required |
PaymentRequiredError | 402 | PAYMENT_REQUIRED | Payment needed |
ForbiddenError | 403 | FORBIDDEN | Access denied |
NotFoundError | 404 | NOT_FOUND | Resource missing |
MethodNotAllowedError | 405 | METHOD_NOT_ALLOWED | Wrong HTTP method |
NotAcceptableError | 406 | NOT_ACCEPTABLE | Cannot produce acceptable response |
ProxyAuthRequiredError | 407 | PROXY_AUTH_REQUIRED | Proxy auth required |
RequestTimeoutError | 408 | REQUEST_TIMEOUT | Client took too long |
ConflictError | 409 | CONFLICT | State conflict |
GoneError | 410 | GONE | Resource permanently deleted |
LengthRequiredError | 411 | LENGTH_REQUIRED | Content-Length required |
PreconditionFailedError | 412 | PRECONDITION_FAILED | Precondition not met |
PayloadTooLargeError | 413 | PAYLOAD_TOO_LARGE | Request body too large |
UriTooLongError | 414 | URI_TOO_LONG | Request URI too long |
UnsupportedMediaTypeError | 415 | UNSUPPORTED_MEDIA_TYPE | Content type not supported |
RangeNotSatisfiableError | 416 | RANGE_NOT_SATISFIABLE | Cannot satisfy Range header |
ExpectationFailedError | 417 | EXPECTATION_FAILED | Cannot meet Expect header |
ImATeapotError | 418 | IM_A_TEAPOT | RFC 2324 |
UnprocessableEntityError | 422 | UNPROCESSABLE_ENTITY | Semantic errors |
LockedError | 423 | LOCKED | Resource locked |
FailedDependencyError | 424 | FAILED_DEPENDENCY | Dependent request failed |
TooEarlyError | 425 | TOO_EARLY | Request replayed too early |
UpgradeRequiredError | 426 | UPGRADE_REQUIRED | Client should upgrade protocol |
PreconditionRequiredError | 428 | PRECONDITION_REQUIRED | Conditional request required |
TooManyRequestsError | 429 | TOO_MANY_REQUESTS | Rate limited |
RequestHeaderFieldsTooLargeError | 431 | REQUEST_HEADER_FIELDS_TOO_LARGE | Headers too large |
UnavailableForLegalReasonsError | 451 | UNAVAILABLE_FOR_LEGAL_REASONS | Legal restriction |
5xx Server Errors
| Class | Status | Default Code | Use Case |
|---|---|---|---|
InternalServerError | 500 | INTERNAL_SERVER_ERROR | Unexpected error |
NotImplementedError | 501 | NOT_IMPLEMENTED | Feature missing |
BadGatewayError | 502 | BAD_GATEWAY | Upstream error |
ServiceUnavailableError | 503 | SERVICE_UNAVAILABLE | Temporarily down |
GatewayTimeoutError | 504 | GATEWAY_TIMEOUT | Upstream timeout |
HttpVersionNotSupportedError | 505 | HTTP_VERSION_NOT_SUPPORTED | HTTP version unsupported |
VariantAlsoNegotiatesError | 506 | VARIANT_ALSO_NEGOTIATES | Content negotiation error |
InsufficientStorageError | 507 | INSUFFICIENT_STORAGE | Insufficient storage |
LoopDetectedError | 508 | LOOP_DETECTED | Infinite loop detected |
NotExtendedError | 510 | NOT_EXTENDED | Extension not available |
NetworkAuthRequiredError | 511 | NETWORK_AUTH_REQUIRED | Network auth required |
5xx Errors Don't Expose Messages
Server errors (5xx) have expose: false by default. In production, clients see "Internal Server
Error" instead of the actual message.
Error Options
All error classes accept an options object:
HttpErrorOptions
| Property | Type | Description |
|---|---|---|
code? | string | Override default error code |
expose? | boolean | Override expose behavior |
details? | Record<string, unknown> | Additional data attached to the error |
cause? | unknown | Original error for error chaining |
Examples
// Custom error code
throw new BadRequestError('Invalid email format', {
code: 'INVALID_EMAIL',
});
// With details
throw new UnprocessableEntityError('Validation failed', {
details: {
fields: [
{ field: 'email', message: 'Invalid format' },
{ field: 'age', message: 'Must be positive' },
],
},
});
// Wrapping original error
try {
await database.query(sql);
} catch (err) {
throw new InternalServerError('Database query failed', {
cause: err,
});
}
// Rate limit with retry-after
throw new TooManyRequestsError('Rate limit exceeded', {
retryAfter: 60, // seconds
});Validation Errors
ValidationError extends NextRushError (not HttpError) and provides structured validation failure reporting. It always returns status 400 with expose: true.
import {
ValidationError,
RequiredFieldError,
TypeMismatchError,
RangeValidationError,
LengthError,
PatternError,
InvalidEmailError,
InvalidUrlError,
} from '@nextrush/errors';
// Validation error with multiple issues
throw new ValidationError([
{ path: 'email', message: 'Required', rule: 'required' },
{ path: 'age', message: 'Must be positive', rule: 'range' },
]);
// Static factory methods
throw ValidationError.fromField('email', 'Invalid format', 'email');
throw ValidationError.fromFields({ email: 'Required', age: 'Must be number' });
// Field-specific errors
throw new RequiredFieldError('email');
throw new TypeMismatchError('age', 'number', 'string');
throw new RangeValidationError('age', 0, 120);
throw new LengthError('password', 8, 100);
throw new PatternError('username', '^[a-z0-9]+$');
throw new InvalidEmailError();
throw new InvalidUrlError();ValidationError provides helper methods for querying issues:
const error = new ValidationError([
{ path: 'email', message: 'Required', rule: 'required' },
{ path: 'age', message: 'Must be positive', rule: 'range' },
]);
error.hasErrorFor('email'); // true
error.getErrorsFor('email'); // [{ path: 'email', ... }]
error.getFirstError('email'); // 'Required'
error.toFlatObject(); // { email: 'Required', age: 'Must be positive' }Factory Functions
Create errors without new:
import {
createError,
badRequest,
unauthorized,
forbidden,
notFound,
conflict,
tooManyRequests,
internalError,
} from '@nextrush/errors';
// Generic factory
throw createError(400, 'Invalid input');
throw createError(404, 'User not found', { code: 'USER_NOT_FOUND' });
// Named factories
throw badRequest('Invalid email');
throw unauthorized('Token expired');
throw forbidden('Admin only');
throw notFound('User not found');
throw conflict('Email already exists');
throw tooManyRequests('Rate limit exceeded');
throw internalError('Database connection failed');Error Utilities
isHttpError()
Type guard to check if an error is an HttpError:
import { isHttpError } from '@nextrush/errors';
try {
await someOperation();
} catch (err) {
if (isHttpError(err)) {
console.log(err.status, err.code);
} else {
throw new InternalServerError('Unexpected error', { cause: err });
}
}getErrorStatus()
Get status code from any error:
import { getErrorStatus } from '@nextrush/errors';
const status = getErrorStatus(error); // 500 if not HttpErrorgetSafeErrorMessage()
Get error message safe for client exposure:
import { getSafeErrorMessage } from '@nextrush/errors';
const message = getSafeErrorMessage(error);
// Returns actual message if expose=true, otherwise generic messageError Middleware
errorHandler()
Global error handling middleware. Place it before your routes so it catches all downstream errors.
import { errorHandler } from '@nextrush/errors';
// Basic usage (logs 5xx as errors, 4xx as warnings)
app.use(errorHandler());
// With options
app.use(
errorHandler({
// Include stack trace in responses (default: false)
includeStack: process.env.NODE_ENV !== 'production',
// Custom error logger
logger: (err, ctx) => {
myLogger.error(err, { path: ctx.path, method: ctx.method });
},
// Custom error response transformer
transform: (err, ctx) => ({
success: false,
error: err.message,
status: ctx.status,
}),
// Handle specific error types
handlers: new Map([
[
SyntaxError,
(err, ctx) => {
ctx.status = 400;
ctx.json({ error: 'Invalid JSON' });
},
],
]),
})
);ErrorHandlerOptions
| Property | Type | Description |
|---|---|---|
includeStack | boolean= false | Include stack trace in error responses |
logger | (error: Error, ctx: Context) => void | Custom error logger. Default logs 5xx as errors and 4xx as warnings. |
transform | (error: Error, ctx: Context) => Record<string, unknown> | Custom error response body transformer |
handlers | Map<ErrorClass, (error: Error, ctx: Context) => void> | Custom handlers for specific error types |
notFoundHandler()
Catch-all 404 handler. Responds with JSON when no route matched and ctx.status is still 404.
import { notFoundHandler } from '@nextrush/errors';
// Add after all routes
app.route('/', router);
app.use(notFoundHandler());
// With custom message
app.use(notFoundHandler('Route not found'));catchAsync()
Deprecated
catchAsync() is redundant — async errors propagate through the middleware chain and are caught
by errorHandler(). Use handlers directly.
// catchAsync is no longer needed. This works without it:
router.get('/users', async (ctx) => {
const users = await db.users.findAll();
ctx.json(users);
});Complete Example
import { createApp } from '@nextrush/core';
import { createRouter } from '@nextrush/router';
import {
errorHandler,
notFoundHandler,
NotFoundError,
BadRequestError,
ConflictError,
} from '@nextrush/errors';
const app = createApp();
const router = createRouter();
// Error handler first (catches all downstream errors)
app.use(
errorHandler({
includeStack: process.env.NODE_ENV !== 'production',
})
);
// Routes
router.get('/users/:id', async (ctx) => {
const user = await db.users.findById(ctx.params.id);
if (!user) {
throw new NotFoundError(`User ${ctx.params.id} not found`);
}
ctx.json(user);
});
router.post('/users', async (ctx) => {
const { email, password } = ctx.body as Record<string, unknown>;
if (!email || !password) {
throw new BadRequestError('Email and password required');
}
const existing = await db.users.findByEmail(email);
if (existing) {
throw new ConflictError('Email already registered');
}
const user = await db.users.create({ email, password });
ctx.status = 201;
ctx.json(user);
});
app.route('/', router);
// 404 handler last
app.use(notFoundHandler());TypeScript Types
import type {
HttpErrorOptions,
ValidationIssue,
ErrorHandlerOptions,
ErrorMiddleware,
ErrorContext,
} from '@nextrush/errors';