If you’ve worked in Node for any length of time, you’ve seen it before: something along the lines of require('../../../lib/util/db/models/system/user) . Now, that might be a bit over-exaggerated to prove a point, but I’m willing to bet that there’s one or two of you out there right now thinking “Oh, I’ve seen worse.” A well-maintained and planned directory structure is a joy to traverse, but can be a righteous pain in the butt to actually work with, on the day-to-day.

There’s a variety of answers to this problem, ranging from the simplest (binding require() to require.main.require , which at least gets rid of all that ../../.. spam), all the way to symlinking your app’s code into the node_modules folder and messing around with .gitignore so it doesn’t accidentally get wrecked on commit. But I’ve started using a method that’s much more elegant and deliberate lately, and I expect to continue using it for all future projects, too.

What if, instead of all that nonsense, you could just write require('@models/system/user') ?

It’s time to set up some aliases.

Configure your aliases

In order to set this up, we’ll need to install and configure the “module-alias” npm package. To install:

npm i --save module-alias

Then, at the very start of your program, before any other code, add:

require('module-alias/register')

Finally, configure your aliases in package.json :

"_moduleAliases": { "@root" : "./src", "@models" : "./src/lib/util/db/models" },

Now, you can manually go through and rewrite your requires, or you can use a tool like

relative-to-alias. You’ll probably have to do some manual work regardless, but a tool like this can remove a lot of the burden. Make sure to commit your work to a branch first, so that if you mess up the syntax, you can just discard all the changes and start fresh.

Fix eslint

One handy rule you should be using in your eslint configuration is import/no-unresolved . This rule comes from the excellent “eslint-plugin-import” npm package, and will barf up a warning if your require() path doesn’t resolve to an installed package or local file. However, if you are using this wonderful tool, it’ll barf all over your aliases. Let’s clean that up.

First, install the “eslint-import-resolver-alias” npm package, and update your eslint config. If it’s not an actual javascript file, now’s a great time to convert it, so that you have access to things like the path module. Ultimately, you’ll end up with something like this:

const { resolve } = require('path'); // ... settings: { 'import/resolver': { alias: [ ['@root', resolve(__dirname, 'src')], ['@models', path.resolve(__dirname, 'src/lib/util/db/models')], ], } },

Eslint should now understand how to parse your aliases, and this rule should go back to being useful.

Fix jest

If you’re doing test-driven development, you’ll need to let your test runner know about your aliases, or it will freak the heck out. I use Jest pretty exclusively, so I’ll detail the fix for that here, but the principles will be the same for the others. Just update the "jest" section of your package.json :

"jest": { "moduleNameMapper": { "^@root(.*)$": "<rootDir>/$1", "^@models(.*)$": "<rootDir>/src/lib/util/db/models/$1", } },

All we’re doing here is setting up a regex for jest to parse when requiring modules. If it passes any one of these, it’ll use provided value in place of the alias. One thing to note is that these regexes are evaluated in order, so if you had an alias for @util/network and one for @util , you’d need to place the network one first, or the raw @util regex would trigger first on all of them and potentially redirect to the wrong location.

Wrapping up

There you have it, a modern node development environment where you can use short, punchy aliases to refer to files, and still have your code pass useful linters and test suites with flying colours.

And it comes with other benefits, too! Did you lock yourself into a weird architecture choice long ago, like sticking a lib folder inside your application src folder and wish you could move it out of there without needing to do a potentially messy find-and-replace against hundreds of files with relative directory madness in them? Easy! Just move the folder and change where your @lib alias points (making sure to update the eslint and jest configs). Everything continues working, just as before, except your code is more maintainable and more satisfying to work on.

This should have been a language feature from day one, but until it gets built-in, we have the next-best thing. And it’s still really, really great. Enjoy all that newly-clean node code!