Search code examples
expressexpress-router

Express Router Middleware Called Multiple Times


Using Express and Express Router. I've got a handful of files in a single directory that all implement Express.Router. I'm dynamically requiring them, adding some middleware and then attached them to a "meta" router and finally calling app.use("/",MetaRouter).

My problem is that the middleware is getting called multiple times for each route.

Here is /index.js:

const express = require('express')
const bodyParser = require('body-parser');
const app = express();
const port = 4000;
var cors = require('cors')
var routes = require('./routes');

app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use(routes);

app.listen(port, '0.0.0.0', () => console.log(`Example app listening on port ${port}!`))

Which requires /routes/index.js:

const fs = require('fs');
const path = require('path');
var express = require('express');
var MetaRouter = express.Router();
var passport = require('passport');

const files = fs.readdirSync(__dirname).filter(file => file !== 'index.js');      

const fileModules = {};

var middleware = function(req, res, next) {
    console.log('In The Middleware');
    next();
  }


files.forEach(file => {
    const { name } = path.parse(file); 
    fileModules[name] = require(`./${name}`);
});

Object.entries(fileModules).forEach(([key, _router]) => {
    _router.use(middleware);
    MetaRouter.use(_router);

});

module.exports = MetaRouter;

And here is an example of one of the router files in the directory:

const BoxRequestService = require("../services/request/box_request_service.js");
var express = require('express');
var router = express.Router();

router.get('/box',
  async function (req, res) {
      res.json(await BoxRequestService.getAll(req, res));
  });

router.get('/box/:id',
  async function (req, res) {
    res.json(await BoxRequestService.findById(req, res));
  }
);

router.put('/box/:id',
  async function (req, res) {
    res.json(await BoxRequestService.update(req, res));
  });

router.post('/box', async function (req, res) {
  res.json(await BoxRequestService.create(req, res));
});

module.exports = router;

Expected Results

So when requesting http://localhost/box, I expect to see "In The Middleware" in the console exactly once.

Actual Results

Instead, I see "In The Middleware" logged multiple times. It also depends on the order in which the files are required. So given this list of files (required in this order):

  • /routes/A.js
  • /routes/B.js
  • /routes/C.js
  • /routes/D.js

If a route in A.js is requested, the middleware is called once. If a route in B.js is requested, the middleware is called twice. If a route in C.js is requested, the middleware is called three times. etc. etc.


Solution

  • Express middleware is attached not per router instance, but per path. Since I wasn't specifying a path, my call to _router.use(middleware); was attached my middleware to all paths. And since I called that in a loop, I was just adding the same middleware multiple times to all paths.

    To fix this, and to still get the benefits of dynamically requiring my individual router files, I made some changes to my code.

    First of all, I added a small "metadata" object to each of my router files. It has two fields. One holds the route "prefix" (the path that each of the routes in the file will begin with) and something that says whether or not the routes in that file need to be authorized (my middleware is doing the authorization).

    Then I export both the router and the metadata from that router file.

    Now back to my `/routes/index.js' file ...

    In there I still dynamically require all of the individual routes, and attach the middelware. However, I use the metadata from the router to attach the router using it's prefix, hence I don't apply the middleware to the same route paths over and over.

    /routes/index.js

    const fs = require('fs');
    const path = require('path');
    const auth_service = require('./../utils/auth_service');
    var express = require('express');
    var MetaRouter = express.Router();
    
    // collect all of the legitimate router files
    const files = fs.readdirSync(__dirname).filter(file => file !== 'index.js');
    
    // pull in each "child" router, attach it to the "meta router" and apply middleware
    files.forEach(file => {
        const { name } = path.parse(file);
        _router = require(`./${name}`);
        MetaRouter.use(_router.router_metadata.endpoint_prefix, auth_service.auth_middleware(_router.router_metadata), _router.router);
    });
    
    module.exports = MetaRouter;
    

    one of my router files

    const BoxRequestService = require("../services/request/box_request_service.js");
    var express = require('express');
    var router = express.Router();
    
    const router_metadata = {
      endpoint_prefix: '/box',
      requires_auth: true
    }
    
    router.get('/:id',
      async function (req, res) {
        res.json(await BoxRequestService.findById(req, res));
      }
    );
    
    router.put('/:id',
      async function (req, res) {
        res.json(await BoxRequestService.update(req, res));
      });
    
    router.post('/', async function (req, res) {
      res.json(await BoxRequestService.create(req, res));
    });
    
    router.get('/',
      async function (req, res) {
        res.json(await BoxRequestService.getAll(req, res));
      });
    
    module.exports = { router, router_metadata }; 
    

    And, here is where I define the middleware

    In here, I use the metadata from the router to determine if the middleware should apply to it's paths.

    var passport = require('passport');
    require('../config/passport')(passport);
    
    let self = module.exports = {
    
        auth_middleware: function(router_metadata){
            return function (req, res, next) {
                if (router_metadata.requires_auth) {
                    passport.authenticate('jwt', { session: false }, function (err, user, info) {
                        if (err) { return next(err); }
                        if (!user) {
                            res.status("401");
                            return res.send("Unauthorized").end();
                        }
                        req.user = user;
                        next();
                    })(req, res, next);
                } else {
                    next();
                }
            }
        }
    
    }