유동
[Express] node-express에서 에러 핸들링 하기 본문
공식 문서 먼저 보고오자
- https://expressjs.com/ko/guide/error-handling.html
위 express 공식 문서에 나와있는 예제를 보면 4개의 매개변수를 받는 middleware라고 정의되어있다.
const errorHandling = (err, req, res, next) => {
}
- (err, req, res, next) 와 같이 4개의 매개변수를 받는 미들웨어를 바로 errorHandling middleware라고 정의한다.
그리고 아래처럼 프로젝트에 루트 파일의 최하단에 선언해준다.
require("dotenv").config();
const express = require("express");
const app = express();
const cookieParser = require("cookie-parser");
const redisClient = require("../config/database/redis");
// 로그
const morgan = require("morgan");
const logger = require("./module/logger");
const morganFormat = process.env.NODE_ENV !== "production" ? "dev" : combined;
// NOTE: morgan 출력 형태 server.env에서 NODE_ENV 설정 production : 배포 dev : 개발
const accountApi = require("./routes/account");
const authApi = require("./routes/auth");
const uploadApi = require("./routes/upload");
const clubApi = require("./routes/club");
const notificationApi = require("./routes/notification");
const boardApi = require("./routes/board");
const generalApi = require("./routes/general");
const promotionApi = require("./routes/promotion");
const noticeApi = require("./routes/notice");
const searchApi = require("./routes/search");
const errorHandling = require("./middleware/errorHandling");
// connect redis client
redisClient.connect();
// global middleware
app.use(morgan(morganFormat, { stream: logger.stream })); // morgan 로그 설정
app.use(express.json());
app.use(cookieParser());
// api call middleware
app.use("/account", accountApi);
app.use("/auth", authApi);
app.use("/upload", uploadApi);
app.use("/club", clubApi);
app.use("/notification", notificationApi);
app.use("/board", boardApi);
app.use("/search", searchApi);
app.use("/general", generalApi);
app.use("/notice", noticeApi);
app.use("/promotion", promotionApi);
// error handling mddleware
app.use((err, req, res, next) => {
const result = {
message: err.message,
}
// 개발환경 전용
console.error(err);
// 500 error
if (!err.status) {
const serverError = new InternerServerException();
return res.status(serverError.status).send({message: serverError.message});
}
return res.status(err.status).send(result);
});
module.exports = app;
루트파일에 로직이 들어가는걸 싫어하는 굉장히 싫어하는 성향을 가지고있다면(나) 미들웨어를 따로 빼놓을수도 있다.
/src/middleware/errorHandling.js
const { InternerServerException } = require("../module/customError");
const errorHandling = () => {
return (err, req, res, next) => {
const result = {
message: err.message,
}
// 개발환경 전용
console.error(err);
// 500 error
if (!err.status) {
const serverError = new InternerServerException();
return res.status(serverError.status).send({message: serverError.message});
}
return res.status(err.status).send(result);
}
}
module.exports = errorHandling;
// error handling middleware
app.use(errorHandling());
현재 개발중인 프로젝트에 동아리 내 모든 일반 게시물을 가져오는 api가 있는데,
이걸 예로 들어보겠다.
해당하는 api에서 발생하는 의미론적 예외는
- 동아리에 가입되어 있지 않은 사용자가 해당 api를 호출 시
- path로 받은 clubId가 존재하지 않는 동아리일 시
- validation예외 시정도 가 있다.
위 나열한 에러들은 400status code와 함께 적절한 메세지를 던져줘야 하는데
나는 일일이 statuscode와 message를 붙여주기 귀찮아서
customError.js파일을 만들고 프로젝트에서 사용하기로 협의된 statuscode 클래스를 미리 정의해줬다.
// customError.js
// 400 error
class BadRequestException extends Error {
constructor(message) {
super(message);
this.name = "BadRequestException";
this.status = 400;
}
}
// 401 error
class UnauthorizedException extends Error {
constructor(message) {
super(message);
this.status = 401;
}
}
// 403 error
class ForbbidenException extends Error {
constructor(message) {
super(message);
this.status = 403;
}
}
// 500 error
class InternerServerException extends Error {
constructor() {
super("서버에서 오류가 발생하였습니다");
this.status = 500;
}
}
module.exports = {
BadRequestException,
UnauthorizedException,
ForbbidenException,
InternerServerException
};
- 각 클래스마다 기본 Error객체를 상속받아주고 status code와 name을 지어준다
// 동아리 내 모든 일반 게시물을 가져오는 api
// 권한: 해당 동아리에 가입되어있어야 함
router.get("/list/club/:clubId", loginAuth, async (req, res, next) => {
const userId = req.decoded.id;
const { clubId } = req.params;
const result = {
message: "",
data: {}
};
const page = Number(req.query.page || 1);
try {
validate(clubId, "clubId").checkInput().isNumber();
validate(page, "page").isNumber().isPositive();
// 권한 체크
const selectAuthSql = ``;
const selectAuthParam = [userId, clubId];
const selectAuthData = await pool.query(selectAuthSql, selectAuthParam);
// 만약 동아리가 존재하지 않는다면?
if (selectAuthData.rowCount === 0) {
throw new BadRequestException("존재하지 않는 동아리입니다");
}
// 만약 동아리에 가입하지 않은 사용자라면?
if (selectAuthData.rows[0].position === null) {
throw new BadRequestException("동아리에 가입하지 않은 사용자입니다");
}
const offset = (page - 1) * POST.MAX_POST_COUNT_PER_PAGE;
const selectAllPostCountSql = ``;
const selectAllPostCountParam = [clubId];
const selectAllPostSql = ``;
const selectAllPostParam = [clubId, offset, CLUB.MAX_ALL_POST_COUNT_PER_PAGE];
const selectAllPostCountData = await pool.query(selectAllPostCountSql, selectAllPostCountParam);
const selectAllPostData = await pool.query(selectAllPostSql, selectAllPostParam);
result.data = {
count: selectAllPostCountData.rows[0].count,
posts: selectAllPostData.rows
}
} catch (error) {
return next(error);
}
res.send(result);
});
해당 API에서 의미론적 예외(400에러) 발생 시에는 조건문으로 잡아줘서 미리 지정해둔 400에러를 throw 해준다.
catch에 있는 next에서 error를 받으면 아래의 errorHadnling미들웨어로 알아서 찾아간다
const { InternerServerException } = require("../module/customError");
const errorHandling = () => {
return (err, req, res, next) => {
const result = {
message: err.message,
}
// 개발환경 전용
console.error(err);
// 500 error
if (!err.status) {
const serverError = new InternerServerException("서버에서 오류가 발생하였습니다");
return res.status(serverError.status).send(serverError.message);
}
return res.status(err.status).send(result);
}
}
module.exports = errorHandling;
여기서 특이한점은 status code를 지정해주지 않은 에러는 전부 500으로 되돌려보냈다.
따라서 DB오류, 백엔드 어플리케이션 오류는 전부 내가 설정한 500에러로 클라이언트에게 전달된다.
- customError객체 입맛대로 만들고
- try-catch내에서 예외처리하고싶을때 customError throw해서 next(error)로 넘기면
- 된다
'node.js > ExpressJS' 카테고리의 다른 글
[Express] 비동기함수에 반복되는 try-catch 없애기 (0) | 2024.01.05 |
---|---|
[Express] 요청 객체를 dto로 변환하기 (0) | 2023.12.13 |
[Express] jwt로 로그인 유지시켜주기 (0) | 2023.11.04 |
[Express] 프론트에서 온 값을 검증하는 exception 모듈 만들기 (0) | 2023.08.02 |
[Express] Cannot read properties of undefined (0) | 2023.07.18 |