import { ZodError } from 'zod'; import logger from '../utils/logger.js'; /** * Centralized error handling middleware * Must be registered LAST in the Express middleware chain */ export const errorHandler = (err, req, res, _next) => { // Log the error logger.error('Unhandled error', { message: err.message, stack: process.env.NODE_ENV === 'development' ? err.stack : undefined, method: req.method, url: req.url, ip: req.ip, }); // Handle Zod validation errors if (err instanceof ZodError) { return res.status(400).json({ success: false, error: 'Validation failed', details: err.errors.map((e) => ({ field: e.path.join('.'), message: e.message, })), }); } // Prisma known errors if (err.code === 'P2002') { return res.status(409).json({ success: false, error: 'A record with this information already exists.', field: err.meta?.target?.[0], }); } if (err.code === 'P2025') { return res.status(404).json({ success: false, error: 'Record not found.', }); } if (err.code === 'P2003') { return res.status(400).json({ success: false, error: 'Invalid reference. The related record does not exist.', }); } // Generic HTTP errors const statusCode = err.statusCode || err.status || 500; const message = statusCode < 500 || process.env.NODE_ENV === 'development' ? err.message : 'An internal server error occurred. Please try again later.'; res.status(statusCode).json({ success: false, error: message, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }), }); }; /** * 404 Not Found handler */ export const notFoundHandler = (req, res) => { res.status(404).json({ success: false, error: `Route not found: ${req.method} ${req.path}`, }); }; /** * Create a custom HTTP error */ export class AppError extends Error { constructor(message, statusCode = 500) { super(message); this.statusCode = statusCode; this.name = 'AppError'; Error.captureStackTrace(this, this.constructor); } }