import type { Request, Response, NextFunction, ErrorRequestHandler } from 'express'; import { logger } from '../utils/logger.js'; import { ZodError } from 'zod'; export interface AppError extends Error { statusCode?: number; isOperational?: boolean; } export function createError(message: string, statusCode: number = 500): AppError { const error: AppError = new Error(message); error.statusCode = statusCode; error.isOperational = true; return error; } export const errorHandler: ErrorRequestHandler = ( err: AppError | Error, req: Request, res: Response, _next: NextFunction ): void => { // Handle Zod validation errors if (err instanceof ZodError) { const message = err.errors.map(e => e.message).join(', '); res.status(400).json({ error: message }); return; } // Get status code and message const statusCode = 'statusCode' in err ? err.statusCode || 500 : 500; const isOperational = 'isOperational' in err ? err.isOperational : false; // Log error if (statusCode >= 500 || !isOperational) { logger.error({ err, method: req.method, url: req.url, statusCode, }, 'Unhandled error'); } else { logger.warn({ message: err.message, method: req.method, url: req.url, statusCode, }, 'Operational error'); } // Send response res.status(statusCode).json({ error: isOperational ? err.message : 'Internal server error', }); }; // Async route handler wrapper export function asyncHandler( fn: (req: Request, res: Response, next: NextFunction) => Promise ): (req: Request, res: Response, next: NextFunction) => void { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; }