Search code examples
javascriptnode.jsexpressmiddleware

Nested router doesn't return next callback with value of 'route'


I'm building out a handler for middleware and I discovered that when you return the route string as an argument for the next callback it's value is null.

Here's the example:

var express = require('express')
var app = express()

app.use('/', function (req, res, next) {
  var router = express.Router()
  router.use(function (req, res, next) {
    return next('route')
  })
  return router(req, res, function (nextValue) {
    console.log('// value of next')
    console.log(nextValue)
    return next(nextValue)
  })
})

app.use('/', function (req, res, next) {
  return res.send('hi')
})

module.exports = app

Which means you can't just pass the next handler in like this:

app.use('/', function (req, res, next) {
  var router = express.Router()
  router.use(function (req, res, next) {
    return next('route')
  })
  return router(req, res, next)
})

I know this looks very redundant because you can just do this:

app.use('/', function (req, res, next) {
   return next('route')
})

However I am building a library that needs to use nested middleware in this fashion. It seems that my only option is to use a different string because If I do this:

  router.use(function (req, res, next) {
    return next('anystring')
  })

The next callback does provide nextValue with anystring.

Why does the string route not propagate through nested middleware?

This seems like it actually makes sense for express to not return route because at that point route that route is finished.


Solution

  • First off .use doesn't support next('route'). So I used .all instead. Even that doesn't return the string "route". So I needed to inject some middleware into the router at the end. If the value of nextRoute is not updated, then next('route') was called sometime during the middleware stack and I can propagate it upward to the parent middleware.

    I found that I'd have to inject a piece of middleware into the end to

    app.use(function (req, res, next) {
      var router = express.Router()
      var nextRoute = true
      router.all('/', [
        function (req, res, next) {
          return next('route')
        },
        function (req, res, next) {
          nextRoute = false
          return res.send('hi')
        },
      ])
      return router(req, res, function (nextValue) {
        if (nextRoute && !nextValue) return next('route')
        return next(nextValue)
      })
    })
    
    app.use('/', function (req, res, next) {
      return res.send('hi')
    })
    

    This allows for my middleware-nest module to work:

    var _ = require('lodash')
    var express = require('express')
    
    /** Allow for middleware to run from within middleware. */
    function main (req, res, next, Router, middleware) {
      var args = _.values(arguments)
      middleware = _.flatten([_.slice(args, 4)])
      Router = (Router) ? Router : express.Router
      var router = Router()
      var nextRoute = true
      middleware.push(function (req, res, next) {
        nextRoute = false
        return next()
      })
      router.all('*', middleware)
      return router(req, res, function (nextValue) {
        if (nextRoute && !nextValue) return next('route')
        return next(nextValue)
      })
    }
    
    main.applyRouter = function (Router) {
      Router = (Router) ? Router : express.Router
      return function () {
        var args = _.values(arguments)
        args.splice(3, 0, Router)
        return main.apply(null, args)
      }
    }
    
    module.exports = main