Simple custom express-like Middleware pipelines


Node’s Express framework has a really good way of enforcing middleware like ‘pipelines’ that allow you to organise and compose your code through the middleware pattern. You’re meant to provide the set of middleware functions that would be sequentially executed from the beginning to the end creating what I here refer to as a middleware pipeline.

Below I’ll give a very simplified way of achieving that type of behaviour, by using a bit of TypeScript.

First you’ll need a higher order function that would be responsible for implementation of a middleware based pipeline processing. The goal is to provide it with a list of middleware functions that would be sequentially executed (similar to express array based middleware):

///////////////////////////
// Defining the middleware
///////////////////////////
const middleware = (functions: Function[] = []): Function => ( ...args) => functions.forEach(fn => fn.apply(this, [...args]));

Than you could set up the data (sequence of middleware functions as well as additional data to be used as context/arguments for each individual middleware function) to test it with:

///////////////////////////
// Using the middleware
///////////////////////////

// Test data to be used as context/arguments for all middleware functions
const req = {type: 'request', data: {}};
const res = {type: 'response', data: {}};

// Definition of custom middleware functions
const setLikes = (...args: any[]) => args[1].data.likes = 0;
const increment = (...args: any[]) => args[1].data.likes++;
const log = (...args: any[]) => console.log.apply(console, args);
const preLog = (...args: any[]) => log(`Before executing function!`, args);
const postLog = (...args: any[]) => log(`After executing function!`, args);

// Compose middleware pipeline by providing a sequence of middleware functions
const muttablePrePostLogMiddlewares = middleware([setLikes, preLog, increment, postLog]);

// Execute above created middleware pipeline on provided dataset
muttablePrePostLogMiddlewares(1, req, res);

And above would cause the following output to be produced:

//////////////////////////
// Produced Output
//////////////////////////
// Before executing function! [1,{"type":"request","data":{"likes":0}},{"type":"response","data":{}}]
// After executing function! [1,{"type":"request","data":{"likes":1}},{"type":"response","data":{}}]

There are many things you could add there in addition to the simple implementation from above. Just as an e.g. if for some reason you would need to have the results of your middleware pipeline execution, per middleware function, you could make sure that your middleware function definition from above returns the middleware pipeline result just by changing forEach to map operator like provided below:

///////////////////////////
// Defining the middleware
///////////////////////////
const middelware = (functions: Function[] = []): Function => ( ...args) => functions.map(fn => fn.apply(this, [...args]));

// ...

///////////////////////////
// Using the middleware
///////////////////////////
// Execute above created middleware pipeline on provided dataset
const middlewarePiplieResult = muttablePrePostLogMiddlewares(1, req, res);
console.log(`Result of middleware pipeline execution: ${result}`);

//////////////////////////
// Produced Output
//////////////////////////
// Result of middleware pipeline execution: 0,,0,

“And that’s all I have to say about that.”

Advertisements