errorHandler.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. const { logger } = require('../utils/logger');
  2. // Custom Error class
  3. class AppError extends Error {
  4. constructor(message, statusCode, isOperational = true) {
  5. super(message);
  6. this.statusCode = statusCode;
  7. this.isOperational = isOperational;
  8. this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
  9. Error.captureStackTrace(this, this.constructor);
  10. }
  11. }
  12. // Global error handler middleware
  13. const errorHandler = (err, req, res, next) => {
  14. let error = { ...err };
  15. error.message = err.message;
  16. // Log error
  17. logger.error('Error occurred:', {
  18. message: err.message,
  19. stack: err.stack,
  20. url: req.originalUrl,
  21. method: req.method,
  22. ip: req.ip,
  23. userAgent: req.get('User-Agent')
  24. });
  25. // Mongoose bad ObjectId
  26. if (err.name === 'CastError') {
  27. const message = 'Resource not found';
  28. error = new AppError(message, 404);
  29. }
  30. // Mongoose duplicate key
  31. if (err.code === 11000) {
  32. const message = 'Duplicate field value entered';
  33. error = new AppError(message, 400);
  34. }
  35. // Mongoose validation error
  36. if (err.name === 'ValidationError') {
  37. const message = Object.values(err.errors).map(val => val.message).join(', ');
  38. error = new AppError(message, 400);
  39. }
  40. // JWT errors
  41. if (err.name === 'JsonWebTokenError') {
  42. const message = 'Invalid token';
  43. error = new AppError(message, 401);
  44. }
  45. if (err.name === 'TokenExpiredError') {
  46. const message = 'Token expired';
  47. error = new AppError(message, 401);
  48. }
  49. // Database connection errors
  50. if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
  51. const message = 'Database connection failed';
  52. error = new AppError(message, 503);
  53. }
  54. // Send error response
  55. const statusCode = error.statusCode || 500;
  56. const status = error.status || 'error';
  57. const message = error.message || 'Something went wrong';
  58. res.status(statusCode).json({
  59. status,
  60. message,
  61. ...(process.env.NODE_ENV === 'development' && {
  62. stack: err.stack,
  63. error: err
  64. })
  65. });
  66. };
  67. // 404 handler
  68. const notFound = (req, res, next) => {
  69. const error = new AppError(`Not found - ${req.originalUrl}`, 404);
  70. next(error);
  71. };
  72. // Async error wrapper
  73. const catchAsync = (fn) => {
  74. return (req, res, next) => {
  75. fn(req, res, next).catch(next);
  76. };
  77. };
  78. // Development error sender (for debugging)
  79. const sendErrorDev = (err, res) => {
  80. res.status(err.statusCode || 500).json({
  81. status: err.status,
  82. error: err,
  83. message: err.message,
  84. stack: err.stack
  85. });
  86. };
  87. // Production error sender (security-conscious)
  88. const sendErrorProd = (err, res) => {
  89. // Operational, trusted error: send message to client
  90. if (err.isOperational) {
  91. res.status(err.statusCode).json({
  92. status: err.status,
  93. message: err.message
  94. });
  95. } else {
  96. // Programming or other unknown error: don't leak error details
  97. logger.error('ERROR 💥', err);
  98. res.status(500).json({
  99. status: 'error',
  100. message: 'Something went wrong!'
  101. });
  102. }
  103. };
  104. module.exports = {
  105. AppError,
  106. errorHandler,
  107. notFound,
  108. catchAsync,
  109. sendErrorDev,
  110. sendErrorProd
  111. };