How to Set Up Absolute Imports in Node.js and TypeScript with Nodemon

Utkarsh DhimanUtkarsh Dhiman
3 min read

TLDR

Switching from relative to absolute imports in TypeScript can greatly enhance code readability and manageability. By updating the tsconfig.json settings to use a fixed path from your project's root, you can simplify import statements. Additionally, when using nodemon and ts-node for development with absolute imports, you need to install tsconfig-paths and configure it in tsconfig.json. Updating these configurations ensures smooth functioning of absolute imports in your TypeScript project.

Introduction

I was working on a TypeScript project when I noticed messy relative imports. I'm sure you too may have stumbled across something like this. Relative imports make our code verbose and hard to understand where things are coming from, especially when files are nested in folders. For every relative import I write, I have to calculate the number of dots and slashes from the current file.

// example of relative imports
import User, { validateUser } from '../../models/user';
import { cors, middleware } from '../../middlewares';
import connectDb from '../../db';

Absolute imports to the rescue, they make your code cleaner and easier to manage by letting you use a fixed path from the root of your project instead of complex relative paths. This improves readability, and avoids confusion from navigating nested directories. For instance:

// example of absolute imports
import User, { validateUser } from '@/models/user';
import { cors, middleware } from '@/middlewares';
import connectDb from '@/db';

To switch from relative to absolute imports, make sure your tsconfig.json has these configurations set to appropriate values:

// tsconfig.json

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",

    // configurations for absolute imports
    "rootDir": "./src",
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "./src/*"
      ] 
    },
    "outDir": "./dist",

    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Also, since we use nodemon for hot module reloading on the development server, it needs ts-node, a TypeScript compiler, to work. This setup works perfectly with relative imports. However, with absolute imports, we need to install another development dependency and add some configuration to tsconfig.json.

# install this package as development dependency
npm install -D tsconfig-paths
// tsconfig.json(final)

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "rootDir": "./src",
    "baseUrl": "./",
    "paths": {
      "@/*": [
        "./src/*"
      ] 
    },
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },

  // This module registers the TypeScript path resolver, 
  // which enables the resolution of TypeScript paths.
  "ts-node": {
    "require": ["tsconfig-paths/register"]
  }
}

Now, if we run the project in development using nodemon, absolute imports work just fine. Here's an example of how the scripts section in package.json looks:

...  
"scripts": {
  "build": "tsc",
  "start": "nodemon ./src/index.ts",
}
...

I'm using tsc for production build and ts-node for development server through nodemon. Deciding between tsc and ts-node requires understanding how they work internally and what purpose they serve. To learn more about it, read here.

0
Subscribe to my newsletter

Read articles from Utkarsh Dhiman directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Utkarsh Dhiman
Utkarsh Dhiman