Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Chris Peterson
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Chris Peterson@override11 )

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.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

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.

Reply to this Comment

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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.