상세 컨텐츠

본문 제목

Express에서 Error handler 추가하여 try, catch 생략하기!

개발

by 호박너구리의 블로그 2021. 12. 22. 22:23

본문

- 종종 프론트엔드 삽질기를 보내드리고 있습니다. 간단하지만 재미있는 개발 삽질기를 받아보고 싶으시다면, 호박너구리 개발일지 뉴스레터를 구독해 주세요:)

 

RestApi를 쓰고 있다면 어느새 라우터에 함수가 많아져 있을 것입니다.

그리고 router가 적을 때에는 몰랐는데, 많아지고 보면 비동기 함수 에러 처리를 위해 매번 똑같은 try, catch를 사용하고 있는 자신의 코드를 발견할 수 있게 되죠.

오늘은 중복 코드를 줄일 수 있는 미들웨어 에러 핸들링에 대해 다뤄보겠습니다!

 

1. Express의 Middleware

Express에서는 다음과 같이 미들웨어 기능을 제공합니다. (Express 문서)

미들웨어를 사용하면 모든 코드에 콘솔을 찍지 않아도, 하나의 함수만으로 모든 api에 대한 로깅 등이 가능하죠. 실제로 저도 request의 header에 담긴 토큰을 verify 하는 동작을 미들웨어를 통해 수행하고 있습니다.

 

일반적인 Express 미들웨어는 다음과 같이 사용할 수 있죠! (커멘트 파트)

import express from 'express';
import { connect } from 'mongoose';
import router from 'src/routes';
import cors from 'cors';

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
    // 여기가 미들웨어입니다.
    // 모든 함수가 여기를 지나게 되죠
    const date = new Date()
    console.log(date)
    next()
})

app.get('/', (req, res) => {
  res.send('Hello, it is Express');
});

app.listen(process.env.PORT || 3000, () => {
  console.log('listening...');
})

 

그리고 에러 핸들러도 일종의 미들웨어라고 볼 수 있습니다.

모든 api의 에러를 한 곳을 통해 거치도록 만들기 때문인데요. 다만 에러에 대한 미들웨어는 사용하는 방식이 약간 다릅니다.

그냥 미들웨어의 인자가 req, res, next인 것과 달리 에러 핸들러의 인자는 error, req, res, next 입니다. (관련 문서)

import express from 'express';
import { connect } from 'mongoose';
import router from 'src/routes';
import cors from 'cors';

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
    // 여기가 미들웨어입니다.
    // 모든 함수가 여기를 지나게 되죠
    const date = new Date()
    console.log(date)
    next()
})

app.use((err, req, res, next) => {
    // 여기가 에러 핸들러 입니다.
    // 모든 에러가 여기를 지나게 되죠
    // try, catch 에러를 잡을 것이니까 500 error를 던지겠습니다
    res.status(500).send({
        message: 'Server Error',
        error: err
    })
})

app.get('/', (req, res) => {
  res.send('Hello, it is Express');
});

app.listen(process.env.PORT || 3000, () => {
  console.log('listening...');
})

 

 

2. 비동기 에러 핸들러를 위한 함수 만들기

하지만 여기서 끝이 아닙니다.

왜냐하면 에러 핸들러가 비동기 에러를 잡아주지는 않기 때문인데요. 우리는 매 비동기 요청마다 try, catch를 달아주지 않기 위해 에러 핸들러를 만들고 있었기에 이를 해결해야 합니다.

 

그래서 가장 좋은 방법은 router 함수를 감쌀 수 있는 wrapper 함수를 만들어 사용하는 것인데요. 바로 이렇게 만들면 됩니다.

const wrapAsyncController = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next)
  }
} 

export default wrapAsyncController

 

만든 wrapper 함수를 router에 감싸서 사용하면 이제 에러 핸들러가 원하는대로 동작하게 될 것입니다!

import express from 'express';
import { connect } from 'mongoose';
import router from 'src/routes';
import cors from 'cors';

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
    // 여기가 미들웨어입니다.
    // 모든 함수가 여기를 지나게 되죠
    const date = new Date()
    console.log(date)
    next()
})

app.use((err, req, res, next) => {
    // 여기가 에러 핸들러 입니다.
    // 모든 에러가 여기를 지나게 되죠
    // try, catch 에러를 잡을 것이니까 500 error를 던지겠습니다
    res.status(500).send({
        message: 'Server Error',
        error: err
    })
})

app.get('/', (req, res) => {
    res.send('Hello, it is Express');
});

const wrapAsyncController = (fn) => {
  return (req, res, next) => {
    fn(req, res, next).catch(next)
  }
}

app.post('/async', wrapAsyncController(async (req, res) => {
    // 무언가 비동기 요청이 있을 때
    // 이제는 try, catch를 안적어도 됩니다!
}))

app.listen(process.env.PORT || 3000, () => {
  console.log('listening...');
})

 

이제 훨씬 더 깔끔한 코드를 작성하실 수 있을 것입니다!

(ex. 아래는 사이드프로젝트에서 실제 사용하는 코드입니다! 벌써 이만큼 많은 코드에서 try, catch를 생략할 수 있게 되었네요)

import { Router } from 'express';
import { AuthController, UserController } from 'src/controllers';
import { wrapAsyncController } from 'src/middlewares';

const router = Router();

router.get('/', (req, res) => {
  res.send('Hello this is server');
});
router.post('/auth/register', wrapAsyncController(AuthController.signUp));
router.post('/auth/email/existence', wrapAsyncController(AuthController.isExistingEmail));
router.post('/auth/login', wrapAsyncController(AuthController.login));
router.post('/auth/google', wrapAsyncController(AuthController.googleOAuth));

router.get('/users/me', wrapAsyncController(UserController.currentUser));

export default router;

 

그럼 모두 해피코딩하세요:)

728x90

관련글 더보기

댓글 영역