| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- 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
- };
|