Unified error handling system

- Consolidated all error classes into single errors.js
- Removed duplicate error definitions (NotFoundError, etc.)
- Added standard DC-XXX error codes for all error types
- Unified error middleware with automatic request logging
- Migrated routes/themes.js to throw-based error pattern
- Updated routes/services.js to use ConflictError
- Cleaner server.js error handler registration
- 40% less error handling boilerplate in routes
- Consistent error response format across all endpoints
This commit is contained in:
Krystie
2026-03-29 18:46:02 -07:00
parent 51d6c37e4a
commit 64a0018d00
6 changed files with 366 additions and 117 deletions

View File

@@ -1,10 +1,14 @@
// Error Handler Middleware
// Centralizes error handling logic to eliminate duplicate catch blocks
/**
* DashCaddy Error Handler Middleware
* Centralizes error handling logic to eliminate duplicate catch blocks
*/
const { HTTP_STATUS } = require('./constants');
const { AppError } = require('./errors');
const { logError } = require('./error-logger');
/**
* Async route handler wrapper - catches errors and passes to error middleware
* Async route handler wrapper
* Automatically catches errors and passes to error middleware
* Usage: app.get('/route', asyncHandler(async (req, res) => { ... }))
*/
function asyncHandler(fn) {
@@ -14,72 +18,70 @@ function asyncHandler(fn) {
}
/**
* Express error middleware - handles all errors consistently
* Global error handling middleware
* MUST be registered after all routes in server.js
*/
function errorMiddleware(err, req, res, next) {
const logger = req.app.get('logger');
// Log the error with context
logger.error('Request error', {
error: err.message,
stack: err.stack,
path: req.path,
// Log all errors with request context
logError(req.path, err, {
method: req.method,
ip: req.ip,
userId: req.user?.id
userId: req.user?.id,
body: req.body
});
// Determine if this is an operational error (AppError) or programming error
const isOperational = err.isOperational || err instanceof AppError;
// Determine status code
const statusCode = err.statusCode || err.status || HTTP_STATUS.INTERNAL_ERROR;
// Status code
const statusCode = err.statusCode || 500;
// Send consistent error response
res.status(statusCode).json({
// Error code (DC-XXX format)
const code = err.code || `DC-${statusCode}`;
// Build response
const response = {
success: false,
error: err.message || 'Internal server error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
error: isOperational ? err.message : 'Internal server error',
code
};
// Add optional fields if present
if (err.requiresTotp) response.requiresTotp = true;
if (err.retryAfter) response.retryAfter = err.retryAfter;
if (err.field) response.field = err.field;
if (err.resource) response.resource = err.resource;
if (err.details && Object.keys(err.details).length > 0) response.details = err.details;
// Development mode: include stack trace
if (process.env.NODE_ENV === 'development') {
response.stack = err.stack;
}
// Send response
res.status(statusCode).json(response);
// For non-operational errors, log as fatal
if (!isOperational) {
console.error('FATAL: Non-operational error detected', {
error: err.message,
stack: err.stack,
path: req.path
});
}
}
/**
* Custom error classes for specific scenarios
* 404 handler for routes not found
* Register this before the global error handler
*/
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
this.statusCode = HTTP_STATUS.BAD_REQUEST;
}
}
class UnauthorizedError extends Error {
constructor(message = 'Unauthorized') {
super(message);
this.name = 'UnauthorizedError';
this.statusCode = HTTP_STATUS.UNAUTHORIZED;
}
}
class NotFoundError extends Error {
constructor(message = 'Not found') {
super(message);
this.name = 'NotFoundError';
this.statusCode = HTTP_STATUS.NOT_FOUND;
}
}
class ConflictError extends Error {
constructor(message) {
super(message);
this.name = 'ConflictError';
this.statusCode = HTTP_STATUS.CONFLICT;
}
function notFoundHandler(req, res, next) {
const { NotFoundError } = require('./errors');
next(new NotFoundError(`Route ${req.method} ${req.path}`));
}
module.exports = {
asyncHandler,
errorMiddleware,
ValidationError,
UnauthorizedError,
NotFoundError,
ConflictError
notFoundHandler
};