Fix Relative Paths in NodeJS Apps

published on in category NodeJS , Tags: nodejs javascript

Most NodeJS apps consist of hundreds of .js files which are included wherever needed using require. While this makes the dependencies of modules really obvious, most of the paths look like this in the end:

const createUuid = require('../../../support/utils/create_uuid');

The problem is that by default NodeJS uses relative paths for local modules (not stuff that’s inside the node_modules folder). Not only does this look really strange, it’s also hard to read and if you ever want to move files around you have to fix all paths in your application to make it work again (and since this is lazily evaluated, you’ll probably miss some for code paths that are not tested). There are some ways fixing it as written up on GitHub.

The NODE_PATH

I’m not the biggest fan of most of them, however. Especially including additional modules to make this work or symlinking folders to make it look like you have proper paths seems to me that it would probably create more problems than it solves. But adjusting the NODE_PATH variable looks like the simplest and most straight-forward approach since this is what we want to achieve in the end. Still the conclusion tells us

Setting application-specific settings as environment variables globally or in your current shell is an anti-pattern if you ask me. E.g. it’s not very handy for development machines which need to run multiple applications.

If you’re adding it only for the currently executing program, you’re going to have to specify it each time you run your app. Your start-app command is not easy anymore, which also sucks.

I think there are ways to mitigate the downsides:

  • "[…] globally or in your current shell" luckily is not the only way to go. When running a script you can set environment variables that are only valid for the script you’re executing, like: NODE_ENV=production node foo.js. This also solves the problem of having to run multiple applications.
  • “If you’re adding it only for the currently executing program, you’re going to have to specifiy it each time you run your app” also looks strange to me as I virtually never start my NodeJS app directly by running node foo.js. Instead, I have a script in the scripts section of package.json that allows me to do something like npm start, so you could do something like that:
{
	"scripts": {
		"start": "NODE_PATH=$NODE_PATH':.' node index.js"
    }
}

Modifying NODE_PATH from within the app

However, I would agree that depending the whole application on a properly set up, non-standard NODE_PATH is a problem and not the cleanest solution. So there are two more possible ways. The first would be to extend the NODE_PATH environment variable right from your application. The problem is that NodeJS sets the path already on bootstrap of the interpreter, so if you would do process.env.NODE_PATH = __dirname, you’d need to reinitialize the paths using require('module').Module._initPath() which is a private NodeJS API and therefore nothing you can rely on.

Wrapping require

There’s one more way (if you’re running Node 10.12.0 or higher) that seems to me like it’s the way to go. As described in “8. The Wrapper”, NodeJS by now offers an API called module.createRequireFromPath that does exactly what the name says: It creates a require function scoped to the path provided. So you can provide a function on the global object (which I think is okayish in this case) that allows you to require a file from the root path of your app like this:

global.requireFromRoot = require('module').createRequireFromPath(__dirname);

If you place this in your app’s entry point, you’ll be able to load files like this:

const createUuid = requireFromRoot('./support/utils/create_uuid');