Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Manually Invoking Express.js Middleware Functions

By Ben Nadel 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.



Reader 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.

@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).