import { Request, Response, NextFunction } from "express"; import { logger } from "../utils/logger"; // 커스텀 에러 클래스 export class AppError extends Error { public statusCode: number; public isOperational: boolean; constructor(message: string, statusCode: number = 500) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } // 에러 핸들러 미들웨어 export const errorHandler = ( err: Error | AppError, req: Request, res: Response, next: NextFunction ): void => { let error = { ...err }; error.message = err.message; // Prisma 에러 처리 if (err.name === "PrismaClientKnownRequestError") { const message = "데이터베이스 요청 오류가 발생했습니다."; error = new AppError(message, 400); } // Prisma 유효성 검증 에러 if (err.name === "PrismaClientValidationError") { const message = "입력 데이터가 유효하지 않습니다."; error = new AppError(message, 400); } // JWT 에러 처리 if (err.name === "JsonWebTokenError") { const message = "유효하지 않은 토큰입니다."; error = new AppError(message, 401); } if (err.name === "TokenExpiredError") { const message = "토큰이 만료되었습니다."; error = new AppError(message, 401); } // 기본 상태 코드 설정 const statusCode = (error as AppError).statusCode || 500; const message = error.message || "서버 내부 오류가 발생했습니다."; // 에러 로깅 logger.error({ message: error.message, stack: error.stack, url: req.url, method: req.method, ip: req.ip, userAgent: req.get("User-Agent"), }); // 응답 전송 res.status(statusCode).json({ success: false, error: { message: message, ...(process.env.NODE_ENV === "development" && { stack: error.stack }), }, }); }; // 404 에러 핸들러 export const notFoundHandler = (req: Request, res: Response): void => { res.status(404).json({ success: false, error: { message: "요청한 리소스를 찾을 수 없습니다.", path: req.originalUrl, }, }); };