../../../../ escaping relative path require hell in Node.js

../../../../../Having/to/write/code/like/this when you're requiring local modules is a sight for sore eyes. Some consider it one of the bigger pain points with Node due to poor readability when trying to figure out where that module is in the directory structure. Even more painful than reading it is having to think about how many levels you have to walk up then back down when you're the one writing the code (some IDE's like newer version of VS Code help with this but not all do).

Fortunately there are easy solutions to fix this. Let's take a look at some:

Break out into npm modules

First, consider if you should make the module its own private npm module.

  • A good candidate for this might be something you have in a helpers folder that's used a lot of places and may be useful to other projects.
  • A poor candidate would be, if you're using something similar to a MVC structure, your views or controllers folders as this would end up breaking up your structure.  Even if you broke out all core components of the MVC structure (models, views, controllers) into their own modulse, I would argue that your business logic should, in most cases, not be in their own module but instead within the project directory structure. Keep npm modules for more library-ish things.

Please note: the following two solutions will require updating for migration to Node's ES Modules implementation. A post on how to do that is forthcoming. If you're just using Node's CommonJS modules right now (i.e. - anything you have to require() then you're good to go with these solutions).

NODE_PATH

If you Google around for NODE_PATH you'll find its use contested amongst the Node community. There are a lot of accusations of it being an "anti-pattern" because you can technically require things that are not locally installed. But that seems like such a rare scenario you'd only encounter it if you were trying to do something hacky. The only other reason you might not want to use it is because it's not supported by Node's ES Modules if you're migrating to (or starting with) those over the CommonJS style.

Despite the above caveats, I still think it's a viable solution for getting around the require path hell. And when I say "around" I'm not implying that this is a hack. Defining NODE_PATH's is what the variable is for.  Let's look at how you can use this in your project:

In package.json add the following under the "scripts" property:

"start": "NODE_PATH=src/ node <entry point file name>"

And that's all there is to it.

You can even specify multiple paths, for example if your test directory lives outside src and you want to require a lot of test specific helpers or something, like so:

"start": "NODE_PATH=src/ node <entry point file name>",
"test": "NODE_PATH=test/ node <test entry point file name>"

Although you'll then run into the same problem of needing to relative require files from your src, but hey you can do this if you want, and it will still run.

Then your require's will be defined starting from src (or whatever directory you defined):

In the above, if the calculator.js service wanted to require from helpers, instead of being

const add = require('../helpers/add')

it would be

const add = require('helpers/add')

which is admittedly not a very deep directory structure but a simple example for demonstrative purposes. You can see how this would be helpful if you have unavoidably deeper structures.

It should be pointed out that you can have these different NODE_PATH's across concurrently-running projects in the same environment. It won't affect your environment.

require() wrapper

Another good solution is using a function to "wrap" the native require() function and setting this function on the global object:

global.requireWrapper = name => {
  return require(__dirname + '/' + name);
}

Note: this function needs to be defined prior to the first requireWrapper() function call, so I always place it in the entry point of the application - at the top of the file! - to ensure this.

So code with relative paths becomes:

const add = requireWrapper('helpers/operations/add');

add(2, 3);

Rather than something potentially like this, depending on where you're importing the add() from:

const add = require('../../../add');

add(2, 3);

I feel comfortable recommending either of these solutions. Try either (or both) in your codebase and see which one you prefer.

However, in order to give some light to other solutions let's take a look at them and why I don't necessarily recommend them.

Local modules

I don't like having to specify file://<module> in package.json. This is an extra step that, for me, is easy to forget. Also, by having some sort of directory structure in my require's, it's easier for me to more clearly know what is and isn't a module like a library and what is business logic.

const add = require('helpers/operations/add') // this is clearly internal
const fs = require('fs') // this is clearly a dependency

Symlinks

Creating a symlink from your node_modules folder to your app folder (or wherever your entry point is) - I don't like this solution because symlinks are not easily visible, thus making it easier to obscure what's going on. I've run into plenty of bugs before where I forgot something was symlinked locally that it ended up just causing a huge headache. It should be noted that symlinks won't work the same in all environments/OS's either and npm doesn't even support showing you WHAT is currently symlinked (again, a potential cause of bugs).

For full code solutions of the above that you can run by just using npm start, drop your email in the form below:

Subscribe for more Node.js content delivered directly to your inbox

No spam ever. Unsubscribe any time.