Files
tasks-backend/middleware/errorHandler.js
2025-12-26 23:56:32 +02:00

249 lines
5.5 KiB
JavaScript

const logger = require('../config/logger');
const { AppError } = require('../utils/AppError');
/**
* Handle Sequelize validation errors
*/
const handleSequelizeValidationError = (error) => {
const errors = error.errors.map(err => ({
field: err.path,
message: err.message,
value: err.value
}));
return {
statusCode: 400,
message: 'Validation error',
errors
};
};
/**
* Handle Sequelize unique constraint errors
*/
const handleSequelizeUniqueConstraintError = (error) => {
const field = error.errors[0]?.path;
const value = error.errors[0]?.value;
return {
statusCode: 409,
message: `${field} '${value}' already exists`
};
};
/**
* Handle Sequelize foreign key constraint errors
*/
const handleSequelizeForeignKeyConstraintError = (error) => {
return {
statusCode: 400,
message: 'Invalid reference to related resource'
};
};
/**
* Handle Sequelize database connection errors
*/
const handleSequelizeConnectionError = (error) => {
return {
statusCode: 503,
message: 'Database connection error. Please try again later.'
};
};
/**
* Handle JWT errors
*/
const handleJWTError = () => {
return {
statusCode: 401,
message: 'Invalid token. Please log in again.'
};
};
/**
* Handle JWT expired errors
*/
const handleJWTExpiredError = () => {
return {
statusCode: 401,
message: 'Your token has expired. Please log in again.'
};
};
/**
* Handle Sequelize errors
*/
const handleSequelizeError = (error) => {
// Validation error
if (error.name === 'SequelizeValidationError') {
return handleSequelizeValidationError(error);
}
// Unique constraint violation
if (error.name === 'SequelizeUniqueConstraintError') {
return handleSequelizeUniqueConstraintError(error);
}
// Foreign key constraint violation
if (error.name === 'SequelizeForeignKeyConstraintError') {
return handleSequelizeForeignKeyConstraintError(error);
}
// Database connection error
if (error.name === 'SequelizeConnectionError' ||
error.name === 'SequelizeConnectionRefusedError' ||
error.name === 'SequelizeHostNotFoundError' ||
error.name === 'SequelizeAccessDeniedError') {
return handleSequelizeConnectionError(error);
}
// Generic database error
return {
statusCode: 500,
message: 'Database error occurred'
};
};
/**
* Send error response in development
*/
const sendErrorDev = (err, res) => {
res.status(err.statusCode).json({
status: err.status,
message: err.message,
error: err,
stack: err.stack,
...(err.errors && { errors: err.errors })
});
};
/**
* Send error response in production
*/
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,
...(err.errors && { errors: err.errors })
});
}
// Programming or unknown error: don't leak error details
else {
// Log error for debugging
logger.error('ERROR 💥', err);
// Send generic message
res.status(500).json({
status: 'error',
message: 'Something went wrong. Please try again later.'
});
}
};
/**
* Centralized error handling middleware
*/
const errorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
// Log the error
logger.logError(err, req);
// Handle specific error types
let error = { ...err };
error.message = err.message;
error.stack = err.stack;
// Sequelize errors
if (err.name && err.name.startsWith('Sequelize')) {
const handled = handleSequelizeError(err);
error.statusCode = handled.statusCode;
error.message = handled.message;
error.isOperational = true;
if (handled.errors) error.errors = handled.errors;
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
const handled = handleJWTError();
error.statusCode = handled.statusCode;
error.message = handled.message;
error.isOperational = true;
}
if (err.name === 'TokenExpiredError') {
const handled = handleJWTExpiredError();
error.statusCode = handled.statusCode;
error.message = handled.message;
error.isOperational = true;
}
// Multer errors (file upload)
if (err.name === 'MulterError') {
error.statusCode = 400;
error.message = `File upload error: ${err.message}`;
error.isOperational = true;
}
// Send error response
if (process.env.NODE_ENV === 'development') {
sendErrorDev(error, res);
} else {
sendErrorProd(error, res);
}
};
/**
* Handle async errors (wrap async route handlers)
*/
const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
/**
* Handle 404 Not Found errors
*/
const notFoundHandler = (req, res, next) => {
const error = new AppError(
`Cannot find ${req.originalUrl} on this server`,
404
);
next(error);
};
/**
* Log unhandled rejections
*/
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', {
promise,
reason: reason.stack || reason
});
// Optional: Exit process in production
// process.exit(1);
});
/**
* Log uncaught exceptions
*/
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception:', {
message: error.message,
stack: error.stack
});
// Exit process on uncaught exception
process.exit(1);
});
module.exports = {
errorHandler,
catchAsync,
notFoundHandler
};