BillTracker/utils/apiError.js

123 lines
2.8 KiB
JavaScript

/**
* Centralized error handling utility for API routes
*
* Standard error format:
* {
* error: 'ErrorType',
* message: 'Human-readable description',
* field: 'optional-field-name',
* code: 'machine-readable-code'
* }
*/
class ApiError extends Error {
constructor(code, message, status = 500, details = {}) {
super(message);
this.name = 'ApiError';
this.code = code;
this.status = status;
this.details = details;
// Extract field name from details if provided
if (details.field) {
this.field = details.field;
}
}
}
/**
* Create a standardized validation error
*/
function ValidationError(message, field, code = 'VALIDATION_ERROR') {
return new ApiError(code, message, 400, { field });
}
/**
* Create a standardized authentication error
*/
function AuthError(message = 'Authentication required', code = 'AUTH_ERROR') {
return new ApiError(code, message, 401);
}
/**
* Create a standardized authorization error
*/
function ForbiddenError(message = 'Access denied', code = 'FORBIDDEN') {
return new ApiError(code, message, 403);
}
/**
* Create a standardized not found error
*/
function NotFoundError(message = 'Resource not found', code = 'NOT_FOUND') {
return new ApiError(code, message, 404);
}
/**
* Create a standardized conflict error
*/
function ConflictError(message = 'Resource conflict', code = 'CONFLICT') {
return new ApiError(code, message, 409);
}
/**
* Create a standardized rate limit error
*/
function RateLimitError(message = 'Too many requests', code = 'RATE_LIMITED') {
return new ApiError(code, message, 429);
}
/**
* Format an error for JSON response
* This ensures consistent error format across all routes
*/
function formatError(err) {
if (err instanceof ApiError) {
const output = {
error: err.code,
message: err.message,
};
if (err.field) output.field = err.field;
return output;
}
// Fallback for non-standard errors (log internally, show safe message)
const safeMessage = err.status === 500
? 'Internal server error'
: err.message || 'An error occurred';
return {
error: err.code || 'ERROR',
message: safeMessage,
};
}
/**
* Express middleware to handle errors centrally
*/
function errorHandler(err, req, res, next) {
const formatted = formatError(err);
const status = err.status || (err instanceof ApiError ? 500 : 500);
// Only log non-500 errors to avoid exposing sensitive info
if (status !== 500) {
console.error(`[error] ${formatted.error}: ${formatted.message}`);
}
if (!res.headersSent) {
res.status(status).json(formatted);
}
}
module.exports = {
ApiError,
ValidationError,
AuthError,
ForbiddenError,
NotFoundError,
ConflictError,
RateLimitError,
formatError,
errorHandler,
};