전역 에러 미들웨어 (Global Error Handler)
비동기 함수 안에서 에러가 발생했는데 try-catch로 잡아주지 않으면 Node.js 서버 프로세스가 통째로 죽어버리는(Crash) 대형 사고가 발생합니다. 이를 방지하기 위한 최후의 보루이자 우아한 에러 처리 패턴이 바로 전역 에러 미들웨어입니다.
⚠️ 주의: 미들웨어 인자 개수
Express에서 전역 에러 처리 미들웨어로 인식되기 위해서는 반드시 4개의 인자 (err, req, res, next)를 가져야 합니다. 만약 next를 생략해서 인자가 3개가 되면, 일반 미들웨어로 취급되어 에러를 잡지 못합니다.
🛡️ 에러 처리 아키텍처 비교
📋 에러 객체(err)의 주요 프로퍼티
| 속성 (Property) |
타입 |
설명 |
사용 예시 |
err.message |
String |
에러에 대한 간단한 텍스트 설명 |
"User not found" |
err.stack |
String |
에러가 발생한 호출 스택(디버깅 용) |
서버 로그 기록용으로 사용 |
err.statusCode (커스텀) |
Number |
명시적으로 지정한 HTTP 상태 코드 |
400, 401, 404 |
전역 에러 핸들러 (Global Error Handler) 시뮬레이션
클라이언트의 요청 타입에 따라 고의로 에러를 발생시키고, 하나의 미들웨어(전역 에러 핸들러)가 모든 에러를 일괄적으로 잡아내어 안전하게 응답하는 과정을 확인해 보세요.
/* === app.js (Express 서버 코드) === */
const express = require('express');
const app = express();
// 1. 비동기 라우터 로직
app.get('/api/data', async (req, res, next) => {
try {
const type = req.query.type;
console.log(`\n[Server] GET /api/data?type=${type} 요청 수신`);
if (type === 'success') {
return res.status(200).json({ message: "정상 처리되었습니다." });
} else if (type === 'clientError') {
// 💡 개발자가 의도적으로 발생시킨 에러
const error = new Error("잘못된 파라미터가 전달되었습니다.");
error.statusCode = 400; // 커스텀 속성 부여
throw error;
} else {
// 💣 예기치 못한 치명적 에러 발생 (DB 다운 등)
throw new Error("데이터베이스 연결 시간 초과!");
}
} catch (err) {
// 에러가 발생하면 catch에서 잡아서 전역 에러 미들웨어로 넘깁니다!
next(err);
}
});
// 2. 🛡️ 전역 에러 처리 미들웨어 (반드시 인자가 4개여야 합니다)
app.use((err, req, res, next) => {
console.log("🚨 [Global Error Middleware] 에러 포착됨!");
// 에러 객체에 상태 코드가 있으면 쓰고, 없으면 500 (서버 에러)
const status = err.statusCode || 500;
// 보안을 위해 500 에러는 클라이언트에게 구체적인 원인을 숨깁니다.
const responseMessage = status === 500
? "서버 내부 오류가 발생했습니다."
: err.message;
console.error(`[Server Log] 상세 에러 원인: ${err.message} (상태코드: ${status})`);
// 클라이언트에게 에러 응답 전송
res.status(status).json({ error: responseMessage });
});
app.listen(3000, () => console.log('서버 실행 중...'));
🌐 Client App (브라우저)
서버로부터 받은 응답 결과:
(요청을 기다리는 중...)