Make a Typescript Middleware Engine
The Middleware pattern is prevalent, especially in the NodeJS ecosystem — and for a good reason! It gives your users a simple interface for extending your software to fit their particular use case and decoupling the sender and receiver, among many other benefits. If you’ve spent any time working with Node’s HTTP/HTTPS libraries, express, KOA, or just about every Node-based web server, then you’ve seen the pattern before.
Today, we’ll dig in and learn more about the middleware pattern by building our very own middleware engine, Typescript edition! In the sections that follow, I’m going to start with code, then offer some explanations into the how and why. Ready? Here we go!
We’ll start with the types that I made to handle the engine. For the most part, they show how the different elements of our engine are going to work.
First, I started by defining the type for the next function. It’s invoked at the end of every middleware module. The Next is essentially a container. In particular, it’s a container for functions. A crucial fact for later!
The middleware module comes next. The main part of the framework. Each middleware encapsulates work to be executed on our context object before executing the next function, as shown in the first example.
Finally, we have the definition for the actual engine, the pipe. It has two functions, use and execute. Use loads the pipe with middleware, while execute runs through all of the middleware in the pipe, using the context object.
Instead of using a class, we will use the revealing module pattern to share our public API. First, we need to declare the stack, an array that will hold all of our middleware functions.
This portion of the code is pretty straight forward. First, we declare our pipeline, the middleware engine, that optionally takes a comma-separated middleware list on instantiation. Next, the stack is defined, then use as a constant. Use takes in a list of middleware and adds them to the stack. Pushing it onto the end.
Next is where the magic happens, the execute function. It’s powered by two key components: a recursive algorithm and closure.
First, on line 2, we declare our index at -1 since 0 acts as the first index of our stack array. Next, we set up our recursive function, handler. We do some basic error checking first to make sure each middleware is only being called once. Then, if the middleware index matches the length of the stack, return the context.
On line 21, We select a middleware function from the stack based on the current index and run it. Note how along with context, we pass the handler function to next. This means that when next() is called in the middleware function, it’s actually invoking another run of the handler function, at index +1. A notice how context is actually defined as an exec's argument, and we pass it around to our inner functions? That’s closure! When a variable is defined in an outer scope and referenced within a nested function.
Finally, on lines 27 and 28, we kick off the recursive handler and wait for it to return with a value. That value is then passed back to the rest of your program.
Now let’s look at the whole module at once!
At the end of the function (line 63), we return use and execute, offering them as our public API. Now let’s see this engine at work!
I hope you enjoyed my little demo here. If you’d like to see the entire project, it’s available on my GitHub!
Thanks for reading!