Search code examples
javascriptexpressexpress-router

Combining many routes using one switch statement


I am creating an app for production use and would like to combine several routes using one switch statement. I currently use it in development and it works perfectly, however I have not seen this approach used before, and would like to know if there is a reason. Is there any issue using this approach? If so, really looking for the why in the answer.

This is what I'd like to do instead of creating multiple routes.

router.post('/save', auth, async (req, res)=>{
    switch(req.body.action) {
        case 'user':
            result = await asyncSaveUser(req.body.data);
            break;
        case 'order':
            result = await asyncSaveOrder(req.body.data);
            break;
        default:
            result = {success:false, data: 'not valid action'};
            break;     
    }
    return res.status(200).json(result);
})

In the API I would create an action.

url: {baseUrl}+'/save'
body: {
    "action":"user",
    "data": {"fn": John, "ln": Doe}
}

Solution

  • Two concerns:

    • That one function will tend to get fairly large over time. Mind you, you've already isolated the logic into separate functions (I'll come back to that in a moment).
    • Saving a user and saving an order are fundamentally different operations, but if the URL is /save either way, that makes logging, reporting, etc. harder. The difference (user vs. order) is buried in the POST data, rather than being in the URL.

    If you do do it all in one function, route parameters can address the logging, etc., concern. E.g.:

    router.post('/users/:action', async function (req, res) { // Added `async`, since you're using `await`
        switch(req.params.action) {
            case 'user':
                result = await asyncSaveUser(req.body.data);
                break;
            case 'order':
                result = await asyncSaveOrder(req.body.data);
                break;
            default:
                result = {success:false, data: 'not valid action'};
                break;     
        }
        return res.status(200).json(result);
    })
    

    Coming back to avoiding letting this function get overlong: You might use a dispatch object:

    const actionDispatch = {
        action: async asyncSaveUser(data) {
            // ...
        },
        user: async asyncSaveUser(data) {
            // ...
        }
    };
    function invalidAction() {
        return {success:false, data: 'not valid action'};
    }
    
    router.post('/users/:action', async function (req, res) {
        const actionFunction = actionDispatch[req.params.action] || invalidAction;
        const result = await actionFunction(req.body.data);
        return res.status(200).json(result);
    });
    

    Of course, the actionDispatch object could start getting overlong. You might break it up more, the downside to that being maintenance (having to list the functions in it when they're defined elsewhere).


    Side note: Beware of passing an async function to something (router.post) that doesn't understand what it returns (a promise). If you do that, you pretty much have to wrap the entire body in try/catch to handle errors (unless there's some middleware handling promises from these handlers).