Setting Up Path Aliases in Node.js Projects with TypeScript

Setting Up Path Aliases in Node.js Projects with TypeScript

ยท

5 min read

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

ย