Express에서 Exception 처리하기

이글의 목적

Express은 공통적인 exception handling 방법을 제공하지 않기 때문에, 모든 함수 내부에 try-catch문을 이용하여 exception을 처리해야 합니다. 모든 함수 내부에 exception 처리 로직을 넣는 것은 반드시 필요하나, 반복적인 코드가 늘어나게 됩니다. 이 글에서는 모든 함수에서 발생하는 exception을 한 곳에서 처리 할 수 있는 방법을 안내합니다.

일반적인 방법

일반적으로 아래와 같이 라우터에 등록하는 모든 라우팅 핸들러 함수는 내부적으로 try-catch문으로 exception 처리를 해야 합니다. 라우팅 핸들러 개수가 적으면 상관 없겠지만, 개수가 많아지면 모든 함수에 try-catch문을 작성하는 것은 힘든 일입니다.

const express = require('express');
const app = express();
const port = 3000;
const router = express.Router();


router.get('/', (req, res) => {
    try{
        //비즈니스 로직
    }catch(e){
        //exception 처리
    }

    res.send('응답');
});

app.use(router);
app.listen(port, () => {
    console.log('서버가 구동되었습니다.');
});

Exception 처리 코드 반복 피하는 방법

Express.Router 클래스는 사용자가 작성한 라우팅 핸들러를 등록받고, HTTP 요청이 오면 해당 함수를 호출하여 처리합니다.

이 글에서 사용하는 방법은 다음과 같습니다. 먼저, Express.Router 클래스를 감싸는 Wrapper 모듈을 만들고, 그 모듈에 라우팅 핸들러를 등록합니다. Wrapper 모듈은 등록받은 라우팅 핸들러를 try-catch문으로 감싸서 Express.Router에 등록을 하는 방식입니다.


// register.js
const add = function (router, method, path, fn) {
    let addFn;
    if(method === 'get'){
        addFn = router.get;
    }else if(method === 'post'){
        addFn = router.post;
    }else if(method === 'delete'){
        addFn = router.delete;
    }else if(method === 'put'){
        addFn = router.put;
    }else{
        throw `지원하지 않은 HTTP Method 입니다.(${method})`;
    }
    addFn.call(router, path, async function (req, res) {
        try {
            await fn(req, res);
        } catch (e) {
            /**
                Exception 처리 공통 로직
                ex: 로깅, 404 응답 등
            **/
        }
    });
};

exports.getRegister = function(){

    const express = require('express');
    const router = express.Router();

    return {
        get: (path, fn) => {
            add(router, 'get', path, fn);
        },
        post: (path, fn) => {
            add(router, 'post', path, fn);
        },
        put: (path, fn) => {
            add(router, 'put', path, fn);
        },
        delete: (path, fn) => {
            add(router, 'delete', path, fn);
        },
        getRouter: () => router
    }
};

위 모듈을 통해 라우팅 핸들러를 등록하면, exception이 발생했을 때 공통적으로 처리해야 할 로직을 각각 핸들러에 작성하지 않고 Wrapper 모듈 한 곳에 작성하여 처리 할 수 있습니다.

const express = require('express');
const app = express();
const port = 3000;
const register = require('./register').getRegister();


register.get('/', (req, res) => {
    //비즈니스 로직
});

register.post('/', (req, res) => {
    //비즈니스 로직
});

app.use(register.getRouter());
app.listen(port, () => {
    console.log('서버가 구동되었습니다.')
});

위 코드와 같이 각 라우팅 핸들러에는 비즈니스 로직만 작성하고, exception이 발생했을 때 공통적으로 처리해야 하는 로직(예: 에러 메시지를 포함한 response 리턴)을 반복적으로 작성하는 것을 피할 수 있습니다.