2주차 팀프로젝트에서 내가 맡은 역할 중 하나인 에러핸들링 모듈 구축에 대한 회고
서버를 사용하는 사용자가 예상과는 다른 방향으로 서버에 데이터를 요청할 때 그에 적합한 에러 메세지를 응답함으로써 내가 구현한 로직을 보다 정확하게 제공하는 목적을 갖고 있다.
예를 들어, MYSQL의 한 테이블의 컬럼이 NOT NULL 필드를 갖고 있는데 MODEL 폴더로 요청이 가기 전에 사전에 내가 적합한 에러를 보낸다면, 사용자는 해당 정보를 입력함으로써 불필요한 통신 소모를 줄이는 등 효과적으로 서버를 운영할 수 있을 것이다.
앞서 예시에서 mysql 데이터베이스 자체가 해당 row를 담지 못하는 상황에서 적합한 에러 핸들링을 하지 않았을 때 나오는 에러는 사실 컴퓨터가 내는 에러이다.
그렇다면 내가 해당 오류를 제어하도록 미리 예외 상황에 맞는 에러 메세지를 만들어냈다면, 해당 에러 핸들링은 내가 통제 가능한 예외 핸들링(Exception Handling)
이라고 볼 수 있다.
try-catch
구문을 활용해 모든 layer에서 에러 핸들링을 할 수 있지만, 같은 코드의 반복 나열은 효율적이지 못하다고 배웠다.
그렇기 때문에 utils layer에 하나의 파일을 활용해 반복적으로 사용될 에러 핸들링 코드를 모와뒀다.
Controller layer에서 사용될 에러 핸들러이다.
controller layer에선 클라이언트의 request 객체를 그대로 받는데, 여기서 사용되는 async-await 문법에 대한 에러를 try-catch로 일일이 묶는 방식 대신 error.js 라는 파일을 만들고 참조하는 방식을 사용했다.
// src/utils
const catchAsync = (asyncFn) => {
return async (req, res, next) => {
try await asyncFn(req, res, next);
catch (err) next(err);
};
};
이런 방식을 통해 try-catch 구문의 반복적인 사용을 막고, catch한 에러는 비동기 방식으로 next 함수를 통해 메인 파일인 app.js로 넘어가 클라이언트에게 적합한 에러로 응답을 준다.
각각의 layer에서 if 문을 통해 특정 상황에 부합하지 않는 요청일 경우에 대비한 에러를 보냈다.
그러나 이 역시 반복되는 작업이 많이 들어가는 로직이 생기기 때문에 하나의 파일에서 핸들링했다.
class ApiError extends Error {
constructor(message, statusCode) {
this.message = message;
this.statusCode = statusCode;
}
};
// Controller
if (!userId || !nickname || !password || !age) throw new ApiError("KEY_ERROR", 400);
이를 통해 모든 layer에서 구현한 에러마다 새로운 에러에 대한 로직을 짜는 상황을 없애 코드량을 줄일 수 있게 했다.
만약 내가 구현한 에러 범위 밖의 에러가 발생할 경우를 대비해 구축했다.
const globalErrorHandler = (err, req, res) => {
res.status(err.statusCode || 500).json({ message: err.message });
};
이를 통해 전체적인 500 에러를 대비하고, 서버가 꺼지지 않도록 구현했다.
여전히 에러 핸들링은 어렵다. 특히, class를 통해 에러 핸들링을 하는 새로운 방식을 구현해 봤는데 해당 부분에 대한 지식이 빈약하다보니 어려움을 느꼈고, 공부가 필요하다!!!