const { logger } = require('../utils/logger'); // Custom Error class class AppError extends Error { constructor(message, statusCode, isOperational = true) { super(message); this.statusCode = statusCode; this.isOperational = isOperational; this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; Error.captureStackTrace(this, this.constructor); } } // Global error handler middleware const errorHandler = (err, req, res, next) => { let error = { ...err }; error.message = err.message; // Log error logger.error('Error occurred:', { message: err.message, stack: err.stack, url: req.originalUrl, method: req.method, ip: req.ip, userAgent: req.get('User-Agent') }); // Mongoose bad ObjectId if (err.name === 'CastError') { const message = 'Resource not found'; error = new AppError(message, 404); } // Mongoose duplicate key if (err.code === 11000) { const message = 'Duplicate field value entered'; error = new AppError(message, 400); } // Mongoose validation error if (err.name === 'ValidationError') { const message = Object.values(err.errors).map(val => val.message).join(', '); error = new AppError(message, 400); } // JWT errors if (err.name === 'JsonWebTokenError') { const message = 'Invalid token'; error = new AppError(message, 401); } if (err.name === 'TokenExpiredError') { const message = 'Token expired'; error = new AppError(message, 401); } // Database connection errors if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') { const message = 'Database connection failed'; error = new AppError(message, 503); } // Send error response const statusCode = error.statusCode || 500; const status = error.status || 'error'; const message = error.message || 'Something went wrong'; res.status(statusCode).json({ status, message, ...(process.env.NODE_ENV === 'development' && { stack: err.stack, error: err }) }); }; // 404 handler const notFound = (req, res, next) => { const error = new AppError(`Not found - ${req.originalUrl}`, 404); next(error); }; // Async error wrapper const catchAsync = (fn) => { return (req, res, next) => { fn(req, res, next).catch(next); }; }; // Development error sender (for debugging) const sendErrorDev = (err, res) => { res.status(err.statusCode || 500).json({ status: err.status, error: err, message: err.message, stack: err.stack }); }; // Production error sender (security-conscious) const sendErrorProd = (err, res) => { // Operational, trusted error: send message to client if (err.isOperational) { res.status(err.statusCode).json({ status: err.status, message: err.message }); } else { // Programming or other unknown error: don't leak error details logger.error('ERROR 💥', err); res.status(500).json({ status: 'error', message: 'Something went wrong!' }); } }; module.exports = { AppError, errorHandler, notFound, catchAsync, sendErrorDev, sendErrorProd };