Node.js - Remove Routes From Express.js 4.x Stack

6 minute read

As of late, I have been developing within Node.js due to work. We are currently in the midst of launching an enterprise website and when development began, we opted to make use of Node.js as the underlying core for the website. The original version was coded in ASP.NET but we chose to ditch the fossil of a language and move onto something more modern and current in today’s web space. But, with that, we had to actually learn Node.js in the process since we were not familiar with it to begin with.

Node.js makes it very easy to get up and running. Installation is a simple download and install package, coming with the base set of applications needed to get started. (Node and npm.)

As our base framework and router package, we chose to use Express.js. It is probably the most well-known web framework available for Node.js based applications which has amazing support and excellent documentation.

During my off time from work, I have been working on a personal framework of my own to use on websites I build for personal use. I use this time to better my Node.js skills and become more familiar with the framework and technologies we are using while working as well. Within my personal framework, I am using Express.js as my underlying framework and route handler.

The Problem

While Express.js does offer amazing documentation and excellent control over what you can and can’t do, it does lack something I feel is fairly important. Express.js is designed with the aspect in mind that you set something and forget it. It is not built on the notion that you will ever want to change something at runtime, in terms of routing. With that, I found an issue with dealing with my personal framework.

In my framework, I have a plugin system that allows users to extend the core framework of my project. Plugins were designed with the intention of being able to be loaded and unloaded at runtime, on the fly. In order to be able to unload a plugin at runtime, a few key factors had to be in place for it to work properly:

  • Plugins would have to be able to unregister their services.
  • Plugins would have to be able to unregister their middleware route additions from the Express.js stack.
  • Plugins would have to be able to unregister their URL routes from the Express.js stack.

Searching around yielded a very lacking response to solve these problems. There were a few similar questions on StackOverfloww, but you know how I feel about that site, which my opinion held true on those questions (as well as my own in a recent post attempt to get some feedback).

So I took to debugging the stack of Express.js myself.

In v4.x.x, Express.js holds its route stack in a somewhat ‘hidden’ variable to prevent direct access from being used by accident. The express route stack can be found by doing something similar to the following:

// Create a new express application instance..
var express = require('express');
var app = express();
 
// Access the express router stack..
var routerStack = app._router.stack;

Once we have the stack we can take a look at how a route is registered to it via the app.use() function. For example, we can have a middleware route, such as a less-middleware compiler, register to the stack like so:

app.use('/public', require('less-middleware')(path.join(__dirname, 'public')));

However, the issue here is that we register an anonymous function to the router stack. This leaves us with no suitable method of determining which route this is in the stack. This is how it looks in the stack after being registered: https://i.imgur.com/8pCLMKu.png

From this, we can see that here is absolutely no real way to tell what this route in the stack even does. This is something that is an issue with removing things at a later time if we felt the need to.

The Solution

Named functions are the solution. When a route is given to the Express.js router stack, it uses the functions name to store the object. In the screenshot above, we saw that the name for the less-middleware was because it was not a defined named function.

However, if we register a named function to the stack, we see a different result, for example:

https://i.imgur.com/RSQPvq5.png

Here, I have a named function called myNamedFunction. Registering this to the stack shows that the name of the function is used in the route object stored in the stack. This is perfect for determining a function in the stack at a later time.

So now we have an understanding that named functions will help fix this problem. But, it is not always the intended method to register something to the stack. Anonymous function implementation makes things much cleaner and easier to deal with. Such as the less-middleware example above, we don’t want to write a full function to just wrap what that does.

Thankfully, there is a trick you can use to wrap an anonymous function into a named one in a single line easily. I do not take credit for this as I did not write it or create it, full credits go to:

Nate Ferrero - https://stackoverflow.com/a/22880379/1080150

We can wrap the function into a named one doing something like this:

namedFunction = function (name, func) {
    return (new Function(`return function (call) {
        return function ${name} () {
            return call(this, arguments);
        }
    }`)())(Function.apply.bind(func));
};

Now when we look at the router stack, we’ll see:

https://i.imgur.com/lfNmYpW.png

Removing At Runtime

Now that I have a suitable method of locating a function in the router stack, I can remove them pretty easily. In my case, I personally have a plugin service that handles the naming of the functions when they are added to the stack. This allows my plugin service to maintain the name used, ensure that it is safe to be used as a function name, and can ensure the removal of those routes when the plugin is unloaded.

When a plugin is loaded, I register some common paths to the router stack such as the public content that plugin may use:

https://i.imgur.com/fEfRe3K.png

Here I use the same name for all the middleware paths. There is no need to give these unique names as Express.js does not really do anything with the names. This makes it easy to remove all of the plugins middleware at once.

When the plugin is asked to unload, I can remove these routes easily using lodash doing:

https://i.imgur.com/lUwZhFp.png

Comments