When writing programs, lines can get long and the art of spaghetti codes starts to unveil itself and we're met with long lines of unmaintainable codes. Luckily for us, most programming languages support modularization: where codes get to be splitted into smaller chunks in different files for maintainability and organisation. Javascript not being an exception, posses this very trait where we get to import files relatively or absolutely. Using the relative path, we can see a basic example such as
import serialise from "../../../utils/serialise";
Now what happens if the "utils" folder changes or the current file is being restructured? you would have to go through every previous implementation and update it. If you don't have access to a good IDE to ease your work, or worse, you have to update a thousand files, and you would easily see the inefficiency of this process. Now, this is where import alias comes into play. You define the values, map them to an actual location, and import them as you please. So you get something like this
import serialise "$utils/serialise";
So regardless of the new location, the current file still maps to the "utils" folder, and should the location of the "utils" folder change, you would just have to update one place (or two ๐). Now let's get started.
In your selected project directory, run the command below to initialise a new npm project
$ npm init -y
Create your folder structure in the format below and update your files
$ tree
.
โโโ package.json
โโโ src
โโโ index.js
โโโ utils
โโโ second.js
โโโ super
โโโ deep
โโโ folder
โโโ serialise.js
5 directories, 4 files
// index.js
import second from "./utils/second.js";
import pretendToSerialise from "./utils/super/deep/folder/serialise.js";
const init = () => {
const value = "First function called";
const serialised = pretendToSerialise(value);
console.log(serialised);
second();
};
init();
// serialise.js
const pretendToSerialise = (valueToSerialise) => {
return "serialised-> " + valueToSerialise;
};
export default pretendToSerialise;
// second.js
import pretendToSerialise from "./super/deep/folder/serialise.js";
const second = () => {
const value = "Second function called";
const serialised = pretendToSerialise(value);
console.log(serialised);
};
export default second;
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/index"
},
"type": "module",
"keywords": [],
"author": "",
"license": "ISC"
}
Now running npm run start
displays the bellow ouput
> test@1.0.0 start
> node src/index
serialised-> First function called
serialised-> Second function called
Like I discussed earlier, if your file (for example; second.js) changes location, you would have more issues to resolve. Now when I move the "second.js" file to the src folder and run npm run start
, I get an error of module not found. You could easily change the import but It's harder to resolve if it's been used by a lot of files. So to resolve this, let's set up Typescript.
Run
npm i -D typescript rimraf
to install the packages as dev dependency.typescript - This will be used to transpile our typescript code to javascript code
rimraf - This will be used to delete our target folder
Run
npx tsc --init
to create the tsconfig.json file used by typescript
In your package.json, update your start script to this.
"start": "rimraf dist && tsc & node ./dist/index.js"
Now add "_moduleAlias" to your package.json and update it to the example below
"_moduleAliases": {
"$utils": "./dist/utils"
}
Now set your tsconfig to this
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"baseUrl": "./",
"paths": {
"$utils/*": [
"src/utils/*"
]
},
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Replace all prior imports from utils with $utils and rename all ".js" file to ".ts". Doing this also means you would have to declare types to prior variables.
// index.ts
import pretendToSerialise from "$utils/super/deep/folder/serialise";
// second.ts
import pretendToSerialise from "$utils/super/deep/folder/serialise";
Initialise your module-alias at the start of your project i.e the index.ts in this case
// index.ts
import "module-alias/register"; // This is needed once per project
import pretendToSerialise from "$utils/super/deep/folder/serialise";
import second from "./second";
const init = () => {
const value = "First function called";
const serialised = pretendToSerialise(value);
console.log(serialised);
second();
};
init();
Now run npm run start
and you should see a successful output
$ npm run start
> test@1.0.0 start
> rimraf dist && tsc && node ./dist/index.js
serialised-> First function called
serialised-> Second function called
๐ Congrats, you just mapped your utils folder to your project. You can view the source code on https://www.github.com/onfranciis/setting-up-path-aliases-in-nodejs-projects-with-typescript
Error?
If you ran into errors while following this tutorial, here are some things to double check.
Tree structure
Ensure your codebase has this structure
tree -I node_modules . โโโ dist โ โโโ index.js โ โโโ second.js โ โโโ utils โ โโโ super โ โโโ deep โ โโโ folder โ โโโ serialise.js โโโ package.json โโโ package-lock.json โโโ src โ โโโ index.ts โ โโโ second.ts โ โโโ utils โ โโโ super โ โโโ deep โ โโโ folder โ โโโ serialise.ts โโโ tsconfig.json 10 directories, 9 files
Note: You're not required to modify anything in the dist folder
Convert to TS
Rename the extensions of the js files in the "src" directory to ".ts"
Complete package
Ensure your package.json has the dependencies of rimraf, typescript, and module-alias
Initialise the module-alias
At the start of your entry file (index.ts in this case) ensure you have initialised the module-alias package
// src/index.ts import "module-alias/register"; ...
Map your routes
Ensure your package.json has the "_moduleAliases" key and valid value
{ ... "_moduleAliases": { "$utils": "./dist/utils" } }
Observe it's mapping to the dist folder since that's where the transpiled javascript code is. Now we have take care of dev environment by mapping it also in "tsconfig.json"
{ "compilerOptions": { ... "paths": { "$utils/*": [ "src/utils/*" ], }, } }
Conclusion
By the end of this, you should be able to set up your path alias and map it to your desired folders. If you learnt something, give this article a heart and share your comments. Good luck and happy mapping ๐
Credit: Photo by Clint Adair on Unsplash