HTTP error handling with status codes, custom error data, structured error responses, and comprehensive error management utilities.
The main error class for HTTP-specific errors with status codes and metadata.
/**
* HTTP error class with status code and additional metadata
*/
class HTTPError extends Error {
/**
* HTTP status code
*/
status: number;
/**
* HTTP status text
*/
statusText: string | undefined;
/**
* Additional HTTP headers to send with error
*/
headers: Headers | undefined;
/**
* Original error that caused this HTTPError
*/
cause: unknown | undefined;
/**
* Custom data associated with the error
*/
data: any | undefined;
/**
* JSON body properties for error response
*/
body: Record<string, unknown> | undefined;
/**
* Flag indicating if error was handled
*/
unhandled: boolean | undefined;
/**
* Create HTTPError with message and optional details
* @param message - Error message
* @param details - Optional error details
*/
constructor(message: string, details?: ErrorDetails);
/**
* Create HTTPError with error details object
* @param details - Error details including status, message, etc.
*/
constructor(details: ErrorDetails);
/**
* Serialize error to JSON for response
* @returns JSON representation of error
*/
toJSON(): ErrorBody;
/**
* Check if input is an HTTPError instance
* @param input - Value to check
* @returns True if input is HTTPError
*/
static isError(input: any): input is HTTPError;
/**
* Create HTTPError with specific status code
* @param status - HTTP status code
* @param statusText - Optional status text
* @param details - Optional additional details
* @returns HTTPError instance
*/
static status(status: number, statusText?: string, details?: ErrorDetails): HTTPError;
}Usage Examples:
import { HTTPError } from "h3";
// Basic error creation
const basicHandler = defineHandler((event) => {
const { id } = getRouterParams(event);
if (!id) {
throw new HTTPError("ID parameter is required", { status: 400 });
}
const user = findUser(id);
if (!user) {
throw HTTPError.status(404, "Not Found", {
data: { resource: "user", id }
});
}
return user;
});
// Error with custom data
const validationHandler = defineHandler(async (event) => {
const body = await readBody(event);
const errors = validateUser(body);
if (errors.length > 0) {
throw new HTTPError({
status: 422,
statusText: "Validation Error",
message: "Invalid user data",
data: { errors, input: body }
});
}
return createUser(body);
});
// Error with headers
const authHandler = defineHandler((event) => {
const token = getHeader(event, "authorization");
if (!token) {
throw new HTTPError("Authentication required", {
status: 401,
headers: new Headers({
"WWW-Authenticate": "Bearer realm=\"api\""
})
});
}
return { authenticated: true };
});
// Error with cause
const externalHandler = defineHandler(async (event) => {
try {
const data = await fetchExternalAPI();
return data;
} catch (originalError) {
throw new HTTPError("External service unavailable", {
status: 503,
cause: originalError,
data: { service: "external-api" }
});
}
});Quick helpers for common HTTP status codes.
// Common status code patterns
const statusHelpers = {
badRequest: (message?: string, data?: any) =>
HTTPError.status(400, "Bad Request", { message, data }),
unauthorized: (message?: string) =>
HTTPError.status(401, "Unauthorized", { message }),
forbidden: (message?: string) =>
HTTPError.status(403, "Forbidden", { message }),
notFound: (resource?: string, id?: string) =>
HTTPError.status(404, "Not Found", {
message: resource ? `${resource} not found` : "Resource not found",
data: { resource, id }
}),
methodNotAllowed: (allowed: string[]) =>
HTTPError.status(405, "Method Not Allowed", {
headers: new Headers({ "Allow": allowed.join(", ") })
}),
conflict: (message?: string, data?: any) =>
HTTPError.status(409, "Conflict", { message, data }),
unprocessable: (errors: any[]) =>
HTTPError.status(422, "Unprocessable Entity", {
message: "Validation failed",
data: { errors }
}),
tooManyRequests: (retryAfter?: number) =>
HTTPError.status(429, "Too Many Requests", {
headers: retryAfter ? new Headers({ "Retry-After": retryAfter.toString() }) : undefined
}),
internalServerError: (message?: string) =>
HTTPError.status(500, "Internal Server Error", { message }),
notImplemented: (feature?: string) =>
HTTPError.status(501, "Not Implemented", {
message: feature ? `${feature} not implemented` : "Not implemented"
}),
serviceUnavailable: (retryAfter?: number) =>
HTTPError.status(503, "Service Unavailable", {
headers: retryAfter ? new Headers({ "Retry-After": retryAfter.toString() }) : undefined
})
};Usage Examples:
// Use status helpers
const resourceHandler = defineHandler((event) => {
const { id } = getRouterParams(event);
if (!id) {
throw statusHelpers.badRequest("ID parameter required");
}
const resource = findResource(id);
if (!resource) {
throw statusHelpers.notFound("resource", id);
}
return resource;
});
// Validation errors
const validationHandler = defineHandler(async (event) => {
const body = await readBody(event);
const errors = validate(body);
if (errors.length > 0) {
throw statusHelpers.unprocessable(errors);
}
return { valid: true };
});
// Rate limiting
const rateLimitHandler = defineHandler((event) => {
const ip = getRequestIP(event);
if (isRateLimited(ip)) {
const resetTime = getRateLimitReset(ip);
throw statusHelpers.tooManyRequests(resetTime);
}
return { allowed: true };
});Handle and format error responses consistently.
/**
* Global error handler pattern
*/
const errorHandler = onError((error, event) => {
console.error(`Error in ${event.url}:`, error);
// Handle HTTPError instances
if (HTTPError.isError(error)) {
const response = error.toJSON();
// Add debug info in development
if (process.env.NODE_ENV === "development") {
response.stack = error.stack;
response.cause = error.cause;
}
return response;
}
// Handle other errors
return {
message: "Internal Server Error",
status: 500,
...(process.env.NODE_ENV === "development" && {
stack: error.stack,
name: error.name
})
};
});Usage Examples:
// Apply global error handler
const app = new H3({
onError: errorHandler
});
// Custom error formatting
const formatError = (error: HTTPError, event: H3Event) => {
const baseResponse = error.toJSON();
return {
...baseResponse,
timestamp: new Date().toISOString(),
path: event.url.pathname,
method: event.req.method,
requestId: event.context.requestId
};
};
// Route-specific error handling
const protectedHandler = defineHandler({
handler: async (event) => {
// Protected logic
return { data: "protected" };
},
onError: [
(error, event) => {
if (HTTPError.isError(error) && error.status === 401) {
// Custom unauthorized handling
return {
error: "Authentication required",
loginUrl: "/login",
timestamp: Date.now()
};
}
}
]
});/**
* Error details for HTTPError construction
*/
interface ErrorDetails {
/**
* HTTP status code
*/
status?: number;
/**
* HTTP status text
*/
statusText?: string;
/**
* Additional headers to send
*/
headers?: HeadersInit;
/**
* Original error cause
*/
cause?: unknown;
/**
* Custom data for error
*/
data?: any;
/**
* Custom error message
*/
message?: string;
}
/**
* Error input type (union of message and details)
*/
type ErrorInput<DataT = any> = string | (ErrorDetails & { data?: DataT });
/**
* JSON error response body
*/
interface ErrorBody<DataT = any> {
/**
* Error message
*/
message: string;
/**
* HTTP status code
*/
status?: number;
/**
* HTTP status text
*/
statusText?: string;
/**
* Custom error data
*/
data?: DataT;
}Implement error recovery and fallback mechanisms.
// Error recovery pattern
const resilientHandler = defineHandler(async (event) => {
try {
// Primary operation
return await primaryService.getData();
} catch (error) {
console.warn("Primary service failed, trying fallback:", error);
try {
// Fallback operation
return await fallbackService.getData();
} catch (fallbackError) {
// Both failed, return error with context
throw new HTTPError("Service unavailable", {
status: 503,
data: {
primaryError: error.message,
fallbackError: fallbackError.message
}
});
}
}
});Collect and aggregate multiple errors.
// Error aggregation pattern
const batchHandler = defineHandler(async (event) => {
const requests = await readBody(event);
const results = [];
const errors = [];
for (const [index, request] of requests.entries()) {
try {
const result = await processRequest(request);
results.push({ index, result });
} catch (error) {
errors.push({
index,
error: HTTPError.isError(error) ? error.toJSON() : { message: error.message }
});
}
}
if (errors.length > 0) {
throw new HTTPError("Batch processing failed", {
status: 207, // Multi-Status
data: { results, errors }
});
}
return { results };
});Implement structured error logging for better debugging.
// Structured error logging
const structuredErrorHandler = onError((error, event) => {
const errorLog = {
timestamp: new Date().toISOString(),
level: "error",
message: error.message,
status: HTTPError.isError(error) ? error.status : 500,
method: event.req.method,
url: event.url.toString(),
userAgent: event.req.headers.get("user-agent"),
ip: getRequestIP(event),
requestId: event.context.requestId,
stack: error.stack,
...(HTTPError.isError(error) && {
data: error.data,
cause: error.cause
})
};
// Log to your preferred logging service
logger.error(errorLog);
// Return sanitized error for client
if (HTTPError.isError(error)) {
return error.toJSON();
}
return {
message: "Internal Server Error",
status: 500,
requestId: event.context.requestId
};
});Integrate with error monitoring services.
// Error monitoring integration
const monitoringErrorHandler = onError(async (error, event) => {
// Send to monitoring service
await errorMonitoringService.captureException(error, {
tags: {
method: event.req.method,
status: HTTPError.isError(error) ? error.status : 500
},
extra: {
url: event.url.toString(),
headers: Object.fromEntries(event.req.headers.entries()),
context: event.context
}
});
// Continue with normal error handling
if (HTTPError.isError(error)) {
return error.toJSON();
}
return { message: "Internal Server Error", status: 500 };
});