How do I combine Connect middleware into one middleware?
Asked Answered
N

6

8

I have a few middlewares that I want to combine into one middleware. How do I do that?

For example:

// I want to shorten this...
app.use(connect.urlencoded())
app.use(connect.json())

// ...into this:
app.use(combineMiddleware([connect.urlencoded, connect.json]))

// ...without doing this:
app.use(connect.urlencoded()).use(connect.json())

I want it to work dynamically -- I don't want to depend on which middleware I use.

I feel like there's an elegant solution other than a confusing for loop.

Nerve answered 28/11, 2013 at 20:46 Comment(0)
W
6

If you like fancy stuff, here is one of possible solutions:

var connect = require('connect')
var app = connect()

function compose(middleware) {
  return function (req, res, next) {
    connect.apply(null, middleware.concat(next.bind(null, null))).call(null, req, res)
  }
}

function a (req, res, next) {
  console.log('a')
  next()
}

function b (req, res, next) {
  console.log('b')
  next()
}

app.use(compose([a,b]))

app.use(function (req, res) {
  res.end('Hello!')
})

app.listen(3000)

Here is what it does: compose function takes array of middleware and return composed middleware. connect itself is basically a middleware composer, so you can create another connect app with middlewares you want: connect.apply(null, middleware). Connect app is itself a middleware, the only problem is that it doesn't have a next() call in the end, so subsequent middleware will be unreachable. To solve that, we need another last middleware, which will call next : connect.apply(null, middleware.concat(last)). As last only calls next we can use next.bind(null, null) instead. Finally, we call resulting function with req and res.

Wallaby answered 28/11, 2013 at 21:57 Comment(2)
Nice, but I'd argue it's more confusing that the loop that the OP wanted to avoid.Calcification
the idea is to abstract composition as a function, implementation is secondary. by the way, here is a package (doesn't depend on connect): npmjs.org/package/connect-composeWallaby
F
29

Express accepts arrays for app.use if you have a path:

var middleware = [connect.urlencoded(), connect.json()];
app.use('/', middleware)

However, if you want a generic combineMiddleware function, you can build a helper easily without any additional libraries. This basically takes advantage of the fact that next is simply a function which takes an optional error:

/**
 * Combine multiple middleware together.
 *
 * @param {Function[]} mids functions of form:
 *   function(req, res, next) { ... }
 * @return {Function} single combined middleware
 */
function combineMiddleware(mids) {
  return mids.reduce(function(a, b) {
    return function(req, res, next) {
      a(req, res, function(err) {
        if (err) {
          return next(err);
        }
        b(req, res, next);
      });
    };
  });
}
Felony answered 17/9, 2015 at 22:27 Comment(1)
Passing an array works with Express, doesn't work with Connect (without Express).Airplane
W
6

If you like fancy stuff, here is one of possible solutions:

var connect = require('connect')
var app = connect()

function compose(middleware) {
  return function (req, res, next) {
    connect.apply(null, middleware.concat(next.bind(null, null))).call(null, req, res)
  }
}

function a (req, res, next) {
  console.log('a')
  next()
}

function b (req, res, next) {
  console.log('b')
  next()
}

app.use(compose([a,b]))

app.use(function (req, res) {
  res.end('Hello!')
})

app.listen(3000)

Here is what it does: compose function takes array of middleware and return composed middleware. connect itself is basically a middleware composer, so you can create another connect app with middlewares you want: connect.apply(null, middleware). Connect app is itself a middleware, the only problem is that it doesn't have a next() call in the end, so subsequent middleware will be unreachable. To solve that, we need another last middleware, which will call next : connect.apply(null, middleware.concat(last)). As last only calls next we can use next.bind(null, null) instead. Finally, we call resulting function with req and res.

Wallaby answered 28/11, 2013 at 21:57 Comment(2)
Nice, but I'd argue it's more confusing that the loop that the OP wanted to avoid.Calcification
the idea is to abstract composition as a function, implementation is secondary. by the way, here is a package (doesn't depend on connect): npmjs.org/package/connect-composeWallaby
T
2

Old question, but the need is still frequent for all the things using middlewares, like connect, express or custom made req/res/next patterns.

This is a very elegant and purely functional solution:

File ./utils/compose-middleware.js:

function compose(middleware) {
  if (!middleware.length) {
    return function(_req, _res, next) { next(); };
  }

  var head = middleware[0];
  var tail = middleware.slice(1);

  return function(req, res, next) {
    head(req, res, function(err) {
      if (err) return next(err);
      compose(tail)(req, res, next);
    });
  };
}

module.exports = compose;

The final result of the compose(middlewareList) is a single middleware that encapsulates the whole chain of middleware initially provided.

Then simply import it and use like this:

File app.js:

var connect = require('connect');
var compose = require('./utils/compose-middleware');

var middleware = compose([
  connect.urlencoded(),
  connect.json()
]);

var app = connect();
app.use(middleware); 
Torus answered 11/2, 2018 at 16:49 Comment(0)
P
2

A simple and native way, and you don't need to install anything.

const {Router} = require('express')

const combinedMiddleware = Router().use([middleware1, middleware2, middleware3])

Then you can use the combinedMiddleware where you want. For example, you may want to run different set of middlewares/handlers for the same route depending on some conditions (a request attributes, for example):

app.get('/some-route', (req, res, next) => {
  req.query.someParam === 'someValue'
    ? combinedMiddleware1(req, res, next)
    : combinedMiddleware2(req, res, next)
})
Piddling answered 4/7, 2021 at 23:52 Comment(0)
P
0

If you're willing to use a library:

https://www.npmjs.org/package/middleware-flow

var series = require('middleware-flow').series;
var app = require('express')();

app.use(series(mw1, mw2, mw2)); // equivalent to app.use(mw1, mw2, mw3);
Pate answered 21/7, 2014 at 21:55 Comment(0)
A
0

Make a list and use a loop.

const connect = require('connect')
const { urlencoded, json } = require('body-parser')

const app = connect()

[ urlencoded(), json() ].forEach(app.use, app)

The second argument of .forEach is used for this, but if you like you can also do the same with:

[ urlencoded(), json() ].forEach(app.use.bind(app))
Airplane answered 29/7, 2021 at 7:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.