Express: One-time middleware?

Created on 3 Dec 2014  Â·  19Comments  Â·  Source: expressjs/express

Is there such a thing as a one-time middleware for express? Something to just run on the first request?

question

Most helpful comment

For now, unless I hear back from you, I'll leave this example for a single one:

app.use(oneTime(middleware))

function oneTime(fn) {
  var done = false
  return function (req, res, next) {
    if (done) {
      next()
      return
    }

    fn(req, res, next)
  }
}

And for a collection:

app.use(multipleOneTime(mw1, mw2, mw3))

function multipleOneTime(arr) {
  var router = express.Router()

  router.use.apply(router, arguments)

  return oneTime(router)
}

function oneTime(fn) {
  var done = false
  return function (req, res, next) {
    if (done) {
      next()
      return
    }

    fn(req, res, next)
  }
}

All 19 comments

No, but a middleware can track state and pass after the first request, becoming a noop.

Yeah but it still slows down the req/res process, right? Once you have a ton of requests, then suddenly it's not just negligible for the couple people on a development/staging server.

Once you have a ton of requests, then suddenly it's not just negligible for the couple people on a development/staging server.

So, it's 100% negligible if you're only talking about a _single_ one-time middleware. Are you intending to have multiple one-time-use middleware? Basically I can definitely help you, but I just need to know your exact use-case to give you a good, performant answer :)

For now, unless I hear back from you, I'll leave this example for a single one:

app.use(oneTime(middleware))

function oneTime(fn) {
  var done = false
  return function (req, res, next) {
    if (done) {
      next()
      return
    }

    fn(req, res, next)
  }
}

And for a collection:

app.use(multipleOneTime(mw1, mw2, mw3))

function multipleOneTime(arr) {
  var router = express.Router()

  router.use.apply(router, arguments)

  return oneTime(router)
}

function oneTime(fn) {
  var done = false
  return function (req, res, next) {
    if (done) {
      next()
      return
    }

    fn(req, res, next)
  }
}

Well it's most applicable for something like Wordpress's check for an admin user before serving the request. I don't need to do it on every request, but my app is all user-based, and before any page can be served, an admin user needs to exist. So I can't really use a specific route middleware. Mostly things similar to that, is there any other usecase? If there isn't, I guess I can just use your code above.

This is the sort of thing you would check at application launch, not in the request pipeline.

On Dec 2, 2014, at 5:52 PM, Ilan Biala [email protected] wrote:

Well it's most applicable for something like Wordpress's check for an admin user before serving the request. I don't need to do it on every request, but my app is all user-based, and before any page can be served, an admin user needs to exist. So I can't really use a specific route middleware. Mostly things similar to that, is there any other usecase? If there isn't, I guess I can just use your code above.

—
Reply to this email directly or view it on GitHub.

Ah, what requests _would_ you do that on? Every request to a full page would need to check for the admin bar, right?

Ah, @ilanbiala , to me it sounds like what you want to do is bundle your entire app into a single router and then do this:

var app = express()
var router = express.Router()

// put everything on router, not app

// then...
var adminUserSetup = false
app.use(function (req, res, next) {
  if (adminUserSetup) {
    router(req, res, next)
    return
  }

  res.statusCode = 503
  res.send(pageExplainingHowtoSetupAdminUser)
})

Alternatively, you can do the same thing at the http level:

var app = express()

// put everything on app as normal

// then...
var adminUserSetup = false
var server = http.createServer(function (req, res) {
  if (adminUserSetup) {
    app(req, res, next)
    return
  }

  res.statusCode = 503
  res.end(pageExplainingHowtoSetupAdminUser)
})

You can even do things like checking if the admin user exists before the server even listens on the port, as @ChiperSoft suggests and can even start up a server that does nothing but show the page, otherwise is the app and then when the admin user is created, you stop the old server and start up the real one.

In the end, the single if statement you need to add is much more performant than if the Express router has to account for routes changing at runtime.

@dougwilson I never really understood the point of having multiple express.Router() instances, can you point me to a good up-to-date article that really shows the power. To me it always seemed like just having the main one is simple yet powerful enough. Am I wrong? Also, why put that middleware on app, and not on the bundled express.Router() instance?

can you point me to a good up-to-date article that really shows the power

I'm not aware of any article that exists, I'm sorry :( There is http://expressjs.com/guide/using-middleware.html but IDK if that really shows enough.

To me it always seemed like just having the main one is simple yet powerful enough. Am I wrong?

No, you're not wrong as all as long as it serves your purpose :) Their purpose is to split up your routing into chunks you can toggle between, reduce request response time, swap out, etc. They have many, many uses. Above I happened to use it to bundle a bunch of middleware into a single function call, for example.

Also, why put that middleware on app, and not on the bundled express.Router() instance?

I'm not 100% sure which example this refers to. If it's the first, you need to have _something_ on the app to call the express.Router() (which is a _new_ router not related at all to what is currently within the app). express.Router() is the same as new express.Router()--it makes a new router not related to the one already in the app.

Okay, cool. I guess since it could be pretty powerful the biggest thing for Express going forward is some really nice Router docs...? Thanks for the help!

Thanks for the help!

No problem, any time :)

going forward is some really nice Router docs

Yes. In fact, our documentation is already leaps over what it used to be and there is continued work to improve them more and more and add more information :)

@dougwilson If I have a middleware like router.all('*', panel.requiresLogin, panel.requiresAuthentication); and I require this router before another, and both use separate router instances and are mounted to the same base path '/', will the middleware of the first router still have an effect on the second router? Shouldn't it not have an effect because the routers are separate instances?

will the middleware of the first router still have an effect on the second router?

Yes.

Shouldn't it not have an effect because the routers are separate instances?

Sure, but how would Express know not to execute your panel.requiresLogin, panel.requiresAuthentication? The path you put it at matched the request.

The better pattern would be to, on non-auth, call next('route') in panel.requiresLogin and panel.requiresAuthentication, then you can simply do app.use(panel.requiresLogin, panel.requiresAuthentication, router) and router would not execute when you called next('route') but the rest of the stuff on app would still execute.

I'm a little confused about how the alternative would work, can you elaborate a little bit more?

Yea :) Sorry, I was trying to work within the small amount of code you provided. For this example, I'm going to implement a bunch of fake things to help illustrate (note, this is just one way to achieve this):

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

var router = express.Router()
router.get('/admin', function (req, res) {
  res.send('admin page')
})
router.get('/profile', function (req, res) {
  res.send('profile page')
})

app.all('*', requiresLogin, requiresAuthentication, router)
app.get('/', function (req, res) {
  res.send('index page')
})

app.listen(3000)

function requiresAuthentication(req, res, next) {
  if (req.query.authenticated) {
    next()
    return
  }

  next('route')
}
function requiresLogin(req, res, next) {
  if (req.query.loggedIn) {
    next()
    return
  }

  next('route')
}

Then for the above, you can use query string parameters to fake the logged in/authenticated states. Calling GET /admin will 404, but GET /admin?loggedIn=1&authenticated=1 will 200; GET / will work as well, since the checks called next('route').

So it seems there is a difference between next() and next('route'). What is it? Next keeps current route handling and the latter just says check the next one?

So there is some information on http://expressjs.com/4x/api.html#app.METHOD about what next('route') is and a little about what it does.

In the example above, the app.all('*', requiresLogin, requiresAuthentication, router) is considered a single "route" with three "handlers". If next() is called in one of those handlers, it moved from left to right through them. If next('route') is called in one of those handlers, Express short-circuits out of that left-to-right flow and moves on to whatever was declated on app next.

For now, unless I hear back from you, I'll leave this example for a single one:

app.use(oneTime(middleware))

function oneTime(fn) {
  var done = false
  return function (req, res, next) {
    if (done) {
      next()
      return
    }

    fn(req, res, next)
  }
}

And for a collection:

app.use(multipleOneTime(mw1, mw2, mw3))

function multipleOneTime(arr) {
  var router = express.Router()

  router.use.apply(router, arguments)

  return oneTime(router)
}

function oneTime(fn) {
  var done = false
  return function (req, res, next) {
    if (done) {
      next()
      return
    }

    fn(req, res, next)
  }
}

Does this function works? I can not understand it. The middleware is being called multiple times anyway.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dmaks9 picture dmaks9  Â·  3Comments

cuni0716 picture cuni0716  Â·  3Comments

nove1398 picture nove1398  Â·  3Comments

haider0324 picture haider0324  Â·  3Comments

Domiii picture Domiii  Â·  3Comments