Search code examples
javascriptnode.jsexpressmiddleware

How to pass parameter only to middleware that need it?


I have an Express application and I'm trying to put all my middleware in its own file. Some of the middleware functions need the db object and some don't.

It's pretty straightforward for the functions that don't need the db object, but given my code structure below, how can I reference the db object in doesNotNeedDbParam since it already has params req, res, and next?

somefile.js:

const router = express.Router()
const doesNotNeedDbParam = require('./middleware')().doesNotNeedDbParam

function foo () {
  // Currently I have to call require and pass in the db object here b/c 
  // it's not set when requiring the function doesNotNeedDbParam
  router.use(require('./middleware')(db).needsDbParam // <-- Is there a better way to do this so that I can require the file above and pass the db object in when it's set?
}

// Setup db object here
foo()

middleware.js

function doesNotNeedDbParam (req, res, next) {
  ...
}

function needsDbParam (req, res, next) {
  // Where do I reference the db variable?
}

module.exports = (db) => {
  return {
    doesNotNeedDbParam: doesNotNeedDbParam,
    needsDbParam: needsDbParam
  }
}

Solution

  • Functional Approach

    I think a good structure for this is to try currying your middleware. This is a pattern practiced by middleware such as body-parser and internally by Express itself with serve-static. This way, you only have to require once, and pass db where you need to, and don't where you don't need it:

    // Instead of declaring each function directly as a middleware function,
    // we declare them as a function that returns a middleware function
    function doesNotNeedDbParam () {
      return function (req, res, next) {
        …
      }
    }
    
    function needsDbParam (db) {
      return function (req, res, next) {
        // You can use db here along with req, res, next
      }
    }
    
    // No need to export a function now
    module.exports = {
      doesNotNeedDbParam,
      needDbParam,
    };
    

    Then, just require:

    const middleware = require('./middleware');
    …
    router.use(middleware.doesNotNeedDbParam()); // Since this doesn't need anything, no argument
    router.use(middleware.needsDbParam(db)); // You can pass db here now
    

    If you're comfortable with ES6+ syntax, you can condense to:

    const doesNotNeedDbParam = () => (req, res, next) => {
      …
    }
    
    const needsDbParam = (db) => (req, res, next) => {
      // Use db here
    }
    // Export object here...
    

    Then:

    const { doesNotNeedDbParam, needsDbParam } = require('./middleware');
    … 
    router.use(doesNotNeedDbParam());
    router.use(needsDbParam(db));
    

    Attach Approach

    There's also another way you can do this, by attaching a property to the req object once. This removes the need to repass db every single time you want it. Many other packages use this strategy. It goes something like this:

    function attachDb (db) { // Still use curry approach here since we want db
      return function (req, res, next) {
        // Attaches the specified db to req directly
        req.db = db;
      }
    }
    
    function needsDbParam (req, res, next) { // No need for currying
      // Now use req.db here
    }
    // Export your other middleware…
    

    Then, use it like so, make sure attachDb is first so that the property is assigned before you use it:

    router.use(attachDb(db)); // Before all other middleware that depend on req.db
    …
    // No need to call because this is already the middleware function, 
    // able to use req.db, which was assigned via attachDb
    router.use(needDbParam);