Search code examples
node.jsasync-awaithandlerwrappermiddleware

Node.js middleware wrap


I am trying to write my own node.js server, so far I have a question.

const asyncHandler = (fn) => async (req, res, next) => {
    try {
        if (!fn) next();
        await fn(req, res, next);
    } catch (e) {
        next(e);
    }
}

const errorHandler = (error, req, res, next) => {
    if (!error.code) {
        error = new InternalServerError(error.message ? error.message : 'Something broke');
    }
    const errorResponse = new MessageResponse(error.code, error.message);
    res.status(errorResponse.code).send(errorResponse.body);
}

These are two middlewares, the first one handles promise rejection the second one is express error handler. I am using first one like this

router.get('/', asyncHandler(async (req, res, next) => {
    let tasks = await TaskService.getAllTasks();
    tasks = tasks.map(task => {
        return task.entitize();
    })
}));

So I need to wrap all route middleware function into an async handler to handle the rejection. But what if I had 10000 route middleware functions? Can I write some code to wrap all route middleware functions into this async handler BY DEFAULT? I mean writing like this

router.get('/', async (req, res, next) => {
    throw new Error('aaa');
    let tasks = await TaskService.getAllTasks();
    tasks = tasks.map(task => {
        return task.entitize();
    })
});

And this middleware is wrapped by asyncHandler? Has anyone done it before? Are there any libs I can use to achieve this? Thx in advance.


Solution

  • You could wrap the original router.get function and do your async/error handling stuff there along with delegating to the original function. Something like this should work:

    const router = express.Router();    
    const _route = router.route.bind(router);
    const methodsToWrap = ['get', 'post', 'patch', 'delete'];
    
    router.route = function(path) {
      const route = _route(path);
      for (const method of methodsToWrap) {
        if (route[method]) {
          route[method] = wrap(route[method]);
        }
      }
      return route;
    };
    
    function wrap(originRouterMethod) {
      return function() {
        const originMiddlewares = [...arguments];
        const wrappedMiddlewares = originMiddlewares.map(fn => {
          if (typeof fn !== `function`) {
            return fn;
          }
    
          return async function(req, res, next) {
            try {
              await fn.apply(null, arguments);
            } catch (err) {
              console.log('Caught error ' + err);
              next(err);
            }
          };
        });
        originRouterMethod.call(this, wrappedMiddlewares);
      };
    }
    

    With that you don't need to add asyncHandler to all your routes in your setup, you can just specify them as:

    ...
    router.get('/', async(req, res, next) => {
        let tasks = await TaskService.getAllTasks();
        tasks = tasks.map(task => {
                return task.entitize();
            });
    });
    ...