Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Rich Armstrong
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Rich Armstrong ( @richarmstrong )

Manually Invoking Express.js Middleware Functions

By on

The other day, I experimented with porting Framework One (FW/1) concepts into an Express.js web application. I love the class-based organization and inversion of control (IoC) that FW/1 offers; and, I wanted to see if something similar could be built on top of the middleware-based control flow that Express.js provides. In that experiment, I ended-up building all of the FW/1 life-cycle hooks as middleware functions because the whole next()-based callback mechanism felt very magical - like a blackbox. In order to peer into that blackbox, I wanted to further experiment with invoking Express.js middleware functions manually. This way, I may have more options in how I consume Express-compatible code going forward.

At its core, Express.js is just a stack of functions that gets called in series. In fact, it's easy to think about Express.js request handling like a Promise chain. Each middleware function in Express.js acts like a resolution handler (if it has 3 arguments) or a rejection handler (if it has 4 arguments). And, just like a Promise chain, each middleware function is capable of switching the request processing back and forth between a resolution and a rejection state (if you squint hard enough).

NOTE: Unlike a promise chain, Express.js middleware doesn't pass resolved values directly onto the next handler. Instead, Express.js middleware acts by augmenting the request and response objects.

So, if I want to invoke Express.js middleware functions manually, it makes sense to try and wrap their execution in a Promise chain, where each middleware function is proxied by an individual Promise callback. This way, the "next()" function that I pass into each middleware handler could act by moving the local Promise into a "resolved" or a "rejected" state. Which, by the way, is a utility function that basically all Node.js Promise libraries offer.

To experiment with this, I created a simple Express.js demo that has a single route handler. And, within that single route handler, I am manually invoking a series of middleware functions before rendering the final response. To do this, I am essentially reducing the collection of functions into a single Promise chain, which I then return to the calling context

// Require our core node modules.
var chalk = require( "chalk" );
var express = require( "express" );
var Q = require( "q" );
var _ = require( "lodash" );

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// I complete in success, synchronously.
function middlewareOne( request, response, next ) {

	console.log( chalk.red.bold( "[ONE]:" ), "running..." );
	next();

}

// I completely in error, synchronously.
function middlewareTwo( request, response, next ) {

	console.log( chalk.red.bold( "[TWO]:" ), "running..." );
	throw( new Error( "Two went boom!" ) );

}

// I completely in error, asynchronously.
function middlewareThree( request, response, next ) {

	console.log( chalk.red.bold( "[THREE]:" ), "running..." );
	setTimeout(
		() => {

			next( new Error( "Three went boom!" ) );

		},
		100
	);

}

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

var app = module.exports = express();

// For this exploration, we only have one route to handle all requests. But, as an
// experiment, the route handler will explicitly invoke the middleware functions as a
// means to demystify how the middleware works.
// --
// NOTE: I am NOT RECOMMENDING that you do this in your Express.js application - I am
// doing this because next() feels a bit too magical and I want to make it mundane. And,
// because I have reasons to want to be able to run it explicitly from within other
// middleware instances.
app.get(
	"/",
	function ( request, response, next ) {

		console.log( chalk.green.bold( "Processing:" ), request.path );

		// Let's define the collection of middleware to invoke explicitly.
		// --
		// NOTE: I am creating an arbitrary array structure to test the flattening.
		var middlewares = [
			middlewareOne,
			[
				middlewareTwo,
				// Since we know that TWO will throw an error, we'll catch it with error
				// handling middleware and turn it back into a "resolved" chain.
				function catchTwo( request, response, error, next ) {

					console.log( chalk.dim.italic( "Two threw an error:", error.message ) );
					next();

				},
				[
					middlewareThree,
					// Since we know that THREE will throw an error, we'll catch it with
					// error handling middleware and turn it back into a "resolved" chain.
					function catchThree( request, response, error, next ) {

						console.log( chalk.dim.italic( "Three threw an error:", error.message ) );
						next();

					}
				]
			]
		];

		// Now, let's manually run the collection of middleware and then pipe the
		// control flow back into the current request resolution.
		invokeMiddleware( middlewares )
			.then(
				() => {

					console.log( chalk.red.bold( "[REQUEST]:" ), "closing..." );
					response.send( "Hello world" );

				}
			)
			.catch( next )
		;

	}
);


// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //


// I invoke the given middlewares and return a promise.
// --
// NOTE: Internally, Express.js DOES NOT USE PROMISES for the middleware; as such,
// using promises will add some performance overhead and a forced asynchronous control
// flow whereas the core middlware may run synchronously.
function invokeMiddleware( middlewares, request, response ) {

	var promise = _
		// Express automatically collapses all of the middleware into a flat array. As
		// such, we want to flatten the given collection before we reduce it.
		.flattenDeep( [ middlewares ] )
		.reduce(
			( chain, handler ) => {

				// If the handler accepts four explicit arguments, it is an error handler;
				// append it to the promise chain as a catch().
				if ( handler.length === 4 ) {

					var tail = chain.catch(
						( error ) => {

							return( invokeHandler( handler, [ request, response, error ] ) );

						}
					);

				// Otherwise, if the handler accepts three explicit arguments, it is a
				// normal handler; append it to the promise chain as a then().
				} else if ( handler.length === 3 ) {

					var tail = chain.then(
						() => {

							return( invokeHandler( handler, [ request, response ] ) );

						}
					);

				// If the handler accepts an unexpected number of arguments, just bypass
				// it, passing the existing chain onto the next handler.
				} else {

					var tail = chain;

				}

				return( tail );

			},
			Q.when() // Initial promise chain.
		)
	;

	return( promise );


	// I wrap the given handler invocation in a promise.
	function invokeHandler( handler, handlerArgs ) {

		var deferred = Q.defer();

		// NOTE: We don't need to worry about synchronous errors during invocation
		// because the handler is already being invoked inside of the Promise handler
		// in the above reduction.
		handler.call( null, ...handlerArgs, deferred.makeNodeResolver() );

		return( deferred.promise );

	}

}

As you can see, the collection of middleware is composed of an arbitrarily nested array. I did this simply because Express.js allows it and I wanted to build that into my invocation strategy. This array is then flattened and reduced into a Promise chain. If the given middleware has three arguments, I treat it like normal middleware and fold it into the Promise chain as a .then() handler. And, if the given middleware has four arguments, I treat it as an error handler and fold it into the Promise chain as a .catch() handler.

If we bootup this Express.js application and make a request to it, we get the following terminal output:

Invoking Express.js middlware functions manually.

As you can see, each of the middleware was successfully invoked - manually - from within my one Express.js route handler. And, each of the error handlers was able to successfully catch the thrown error and convert the Promise chain wrapper back to a resolved state so that our top-level router handler could successfully complete the request.

To be clear, I am not recommending that people start invoking Express.js middleware manually. Middleware makes sense as "middleware". I just wanted to experiment with this because I can think of some situations in which I may want to leverage existing middleware functions in a context where the middleware architecture has been abstracted away from direct use. And, if nothing else, I feel that experiments like this make Express.js and middleware seem like less of a blackbox, which is good for the old mental model.

Want to use code from this post? Check out the license.

Reader Comments

15,674 Comments

@All,

For what it's wroth, Scott Rippey on my team had a thought that this approach would be helpful for unit-testing middleware since you wouldn't need to spin up / mock-out an entire Express app just see if the inputs / outputs of middleware make sense.

15,674 Comments

@All,

CAUTION: I just realized that my *** ERROR *** object is in the wrong parameter location. It is supposed to be in the FIRST parameter, but I am passing it as the THIRD. In my demo, it happens to work because I am manually invoking the handler (so I basically messed it up on both sides of the call-stack).

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel