웹 애플리케이션을 개발할 때는 에러 핸들링과 응답 구조 설계가 매우 중요합니다. 사용자가 불필요한 오류 메시지에 혼란을 느끼지 않도록 하고, 발생하는 모든 문제를 일관되게 처리할 수 있어야 합니다. 본 강좌에서는 Express.js를 이용한 에러 핸들링과 응답 구조 설계에 대하여 상세히 설명하겠습니다.
1. Express.js 소개
Express.js는 Node.js를 위한 웹 애플리케이션 프레임워크로, 빠르고 간단하게 웹 서버를 구축할 수 있도록 설계되었습니다. RESTful API, 웹 서버, 대규모 웹 애플리케이션 등을 개발할 때 널리 사용되며, ORM과 DB와의 통합, 미들웨어와의 결합 등 유연한 구조를 지원합니다.
2. 에러 핸들링의 중요성
에러 핸들링은 애플리케이션의 안정성과 사용자 경험에 큰 영향을 미칩니다. 예상치 못한 오류가 발생했을 때, 사용자에게 적절한 피드백을 제공하고 시스템의 안전한 동작을 보장해야 합니다. 적절한 에러 핸들링이 이루어지지 않으면, 사용자는 혼란을 느끼고 불만을 가질 수 있습니다.
2.1. 에러 종류
애플리케이션에서 발생할 수 있는 오류는 크게 다음과 같이 나눌 수 있습니다:
- 사용자 입력 오류: 사용자가 잘못된 데이터를 입력했을 경우
- 서버 오류: 서버에서 발생하는 예기치 못한 오류
- 네트워크 오류: 외부 API 호출이나 데이터베이스 연결에서 발생하는 오류
3. Express에서의 에러 핸들링
Express에서는 미들웨어를 통해 에러를 처리할 수 있습니다. 에러 핸들링 미들웨어는 일반적인 미들웨어와 유사하지만, 추가적인 매개변수로 에러 객체를 받아들입니다. 기본적인 에러 핸들러의 구조는 다음과 같습니다.
app.use((err, req, res, next) => {
// 에러 처리 로직
res.status(err.status || 500);
res.json({ error: err.message });
});
3.1. 기본 에러 핸들링
다음은 기본적인 에러 핸들링의 예시입니다. 사용자가 로그인할 때, 인증 정보가 유효하지 않으면 에러 메시지를 클라이언트에게 반환합니다.
app.post('/login', (req, res, next) => {
const { username, password } = req.body;
if (!username || !password) {
const error = new Error('사용자 이름과 비밀번호를 입력하세요.');
error.status = 400;
return next(error);
}
// ... 인증 로직 ...
const isAuthenticated = false; // 예시로 잘못된 인증 결과
if (!isAuthenticated) {
const error = new Error('인증 정보가 올바르지 않습니다.');
error.status = 401;
return next(error);
}
res.send('로그인 성공');
});
4. 사용자 정의 에러 객체
Express에서는 에러 객체를 커스터마이즈하여 발생하는 에러의 유형을 분류할 수 있습니다. 예를 들어, 네트워크 오류나 데이터베이스 오류를 표현하기 위해 다음과 같이 사용자 정의 에러 객체를 생성할 수 있습니다.
class CustomError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
app.get('/data', (req, res, next) => {
// 데이터베이스 연결 오류 발생
const error = new CustomError('데이터베이스 연결 실패', 500);
return next(error);
});
4.1. 사용자 정의 에러 핸들러
사용자 정의 에러 객체를 활용하여 에러 핸들링 로직을 더욱 세분화할 수 있습니다.
app.use((err, req, res, next) => {
if (err instanceof CustomError) {
res.status(err.status).json({ message: err.message });
} else {
res.status(500).json({ message: '서버 오류' });
}
});
5. 응답 구조 설계
클라이언트와 서버 간의 일관된 데이터 구조를 유지하는 것은 매우 중요합니다. 일반적으로 JSON 형식으로 데이터를 송신하며, 응답 구조는 다음과 같이 설계할 수 있습니다:
{
"status": "success",
"data": { /* 관련 데이터 */ },
"message": "성공적으로 처리되었습니다."
}
5.1. 표준화된 응답 구조
모든 API 응답에 대해 표준화된 구조를 제공하는 것이 좋습니다. 예를 들어, 성공적인 결과와 에러 결과를 모두 동일한 형태로 반환하도록 설정할 수 있습니다.
const sendResponse = (res, status, data, message) => {
res.status(status).json({
status: status >= 400 ? 'error' : 'success',
data: data || null,
message: message || ''
});
};
app.get('/items', (req, res) => {
// 데이터 조회
const items = [{ id: 1, name: 'item1' }];
sendResponse(res, 200, items, '아이템 목록 조회 성공');
});
6. 에러 로그 기록
에러가 발생할 경우 이를 로그로 기록하는 것은 매우 중요합니다. 에러를 추적하고, 문제를 신속하게 해결할 수 있도록 돕습니다. Winston과 같은 로깅 라이브러리를 사용할 수 있습니다.
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log' })
],
});
app.use((err, req, res, next) => {
logger.error(err.message);
res.status(err.status || 500).json({ message: '서버 오류' });
});
7. 결론
에러 핸들링과 응답 구조 설계는 안정적이고, 사용자 친화적인 웹 애플리케이션을 개발하는 데 필수적입니다. Express.js의 유연한 미들웨어 기능을 활용하여 다양한 유형의 에러를 효과적으로 처리하고, 일관된 데이터 구조를 적용하면 개발 효율성을 높일 수 있습니다.
본 강좌가 여러분의 Express 개발 실력을 한 단계 끌어올리는 데 도움이 되었기를 바랍니다. 다음 강좌에서는 더 심화된 내용을 다뤄보겠습니다.