errors.js•4.12 kB
/**
* Custom error classes for the application
*/
/**
* Base application error class
*/
class AppError extends Error {
constructor(message, statusCode = 500, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
// Capture stack trace
Error.captureStackTrace(this, this.constructor);
}
}
/**
* Validation error class
*/
class ValidationError extends AppError {
constructor(message, errors = []) {
super(message, 400);
this.errors = errors;
}
}
/**
* Authentication error class
*/
class AuthenticationError extends AppError {
constructor(message = 'Authentication failed') {
super(message, 401);
}
}
/**
* Authorization error class
*/
class AuthorizationError extends AppError {
constructor(message = 'Access denied') {
super(message, 403);
}
}
/**
* Not found error class
*/
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
/**
* Conflict error class
*/
class ConflictError extends AppError {
constructor(message = 'Resource conflict') {
super(message, 409);
}
}
/**
* Rate limit error class
*/
class RateLimitError extends AppError {
constructor(message = 'Too many requests') {
super(message, 429);
}
}
/**
* Database error class
*/
class DatabaseError extends AppError {
constructor(message = 'Database error') {
super(message, 500);
}
}
/**
* External service error class
*/
class ExternalServiceError extends AppError {
constructor(message = 'External service error') {
super(message, 502);
}
}
/**
* Global error handler for Express
*/
const globalErrorHandler = (err, req, res, next) => {
// Default error values
let error = { ...err };
error.message = err.message;
// Log error
console.error('Error:', err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = new NotFoundError(message);
}
// Mongoose duplicate key
if (err.code === 11000) {
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
const message = `Duplicate field value: ${value}. Please use another value`;
error = new ConflictError(message);
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(val => val.message);
error = new ValidationError('Validation failed', errors);
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
error = new AuthenticationError('Invalid token');
}
if (err.name === 'TokenExpiredError') {
error = new AuthenticationError('Token expired');
}
// Send error response
res.status(error.statusCode || 500).json({
success: false,
error: {
message: error.message,
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
...(error.errors && { errors: error.errors }),
},
});
};
/**
* Async error handler wrapper
*/
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
/**
* Create error response
*/
const createErrorResponse = (message, statusCode = 500, errors = null) => {
const response = {
success: false,
error: {
message,
statusCode,
},
};
if (errors) {
response.error.errors = errors;
}
return response;
};
/**
* Create success response
*/
const createSuccessResponse = (data, message = 'Success') => {
return {
success: true,
message,
data,
};
};
/**
* Handle 404 for unknown routes
*/
const handleNotFound = (req, res, next) => {
const error = new NotFoundError(`Route ${req.originalUrl} not found`);
next(error);
};
module.exports = {
AppError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ConflictError,
RateLimitError,
DatabaseError,
ExternalServiceError,
globalErrorHandler,
asyncHandler,
createErrorResponse,
createSuccessResponse,
handleNotFound,
};