import { Request, Response, NextFunction } from 'express'; export class AppError extends Error { constructor( message: string, public readonly statusCode: number, public readonly code: string, public readonly isOperational = true, ) { super(message); this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } } export class ValidationError extends AppError { constructor(message: string, public readonly details?: unknown) { super(message, 400, 'VALIDATION_ERROR'); } } export class AuthenticationError extends AppError { constructor(message = 'Unauthorized') { super(message, 401, 'AUTHENTICATION_ERROR'); } } export class ForbiddenError extends AppError { constructor(message = 'Forbidden') { super(message, 403, 'FORBIDDEN'); } } export class NotFoundError extends AppError { constructor(resource: string) { super(`${resource} not found`, 404, 'NOT_FOUND'); } } export class ConflictError extends AppError { constructor(message: string) { super(message, 409, 'CONFLICT'); } } export class RateLimitError extends AppError { constructor() { super('Too many requests', 429, 'RATE_LIMIT'); } } export function globalErrorHandler( err: Error, req: Request, res: Response, _next: NextFunction, ): void { const logger = (req as Request & { log?: { warn: Function; error: Function } }).log; if (err instanceof AppError && err.isOperational) { if (logger) { logger.warn({ err, statusCode: err.statusCode }, err.message); } const body: Record = { error: err.message, code: err.code }; if (err instanceof ValidationError && err.details !== undefined) { body['details'] = err.details; } res.status(err.statusCode).json(body); return; } if (logger) { logger.error({ err }, 'Unhandled error'); } else { console.error('Unhandled error', err); } res.status(500).json({ error: process.env['NODE_ENV'] === 'production' ? 'Internal server error' : err.message, code: 'INTERNAL_ERROR', }); }