To main content

Absolute imports with module path aliases

When importing a resource from another location in your project you can choose to work with relative or absolute paths. From my own experience relative paths are by far the most used method to handle imports but when projects are growing in size and folder structures get more complex the relative paths lead to an infinite amount of ‘../’ and it can be back-breaking to find the resource.

Absolute vs Relative

Absolute paths will always include the complete directory list from the root of the project to the file, for example /src/foo/bar/shared

Relative paths on the other hand will always consider the current file as root in relation to the resources that it imports. Using relative paths make it so the same resource will have different import paths depending on the importing files location.

For example if we want to import something from /src/foo/shared in /src/bar/ the path would be ../foo/shared but for /src/bar/relative the path would instead be ../../foo/shared.

Configured absolute imports in Typescript

Both absolute and relative paths have their pros and cons when used for imports but in typescript we can discard the two previous options and instead go for a third option that with small configurations will handle imports in a way that will clean up your imports and make it much easier to read, interpret and find the imported resource.

With just a few simple configurations in your tsconfig.json we can go from this:

import ForbiddenError from '../../../errors/Forbidden'
import InvalidEntityError from '../../../errors/InvalidEntity'
import NotFoundError from '../../../errors/NotFound'
import { getPostById } from '../../../repository/post'
import { getUserById } from '../../../repository/user'
import { createLike } from '../../../repository/like'
import { generateError, generateSuccess } from '../../shared/responseHelper'

To this:

import ForbiddenError from '@errors/Forbidden'
import InvalidEntityError from '@errors/InvalidEntity'
import NotFoundError from '@errors/NotFound'
import { getPostById } from '@repository/post'
import { getUserById } from '@repository/user'
import { createLike } from '@repository/like'
import { generateError, generateSuccess } from '@shared/responseHelper'

How to configure your project

In your tsconfig.json just add the root as baseUrl and desired paths to the compilerOptions object and you are set.

"baseUrl": ".",
"paths": {
  "@helpers/*": [
    "src/helpers/*"
  ],
  "@errors/*": [
    "src/errors/*"
  ],
  "@types/*": [
    "src/types/*"
  ],
  "@repository/*": [
    "src/repository/*"
  ],
  "@shared/*": [
    "src/functions/shared/*"
  ],
  "@middleware/*": [
    "src/functions/middleware/*"
  ]
}

Depending on what compiler or module bundler you use it might interpret the imports as packages and try to find a node_modules inside the folders and when it is missing the compilation will break.

In this project we use webpack as module bundler and the interpretation problem can be resolved quite easily by adding the name and path as alias in webpack.config.js

module.exports = {
  resolve: {
    extensions: ['.js', '.ts', '.graphql', '.gql'],
    alias: {
      '@helpers': path.resolve(__dirname, 'src/helpers'),
      '@errors': path.resolve(__dirname, 'src/errors'),
      '@types': path.resolve(__dirname, 'src/types'),
      '@repository': path.resolve(__dirname, 'src/repository'),
      '@shared': path.resolve(__dirname, 'src/functions/shared'),
      '@middleware': path.resolve(__dirname, 'src/functions/middleware'),
      '@': resolve('/src')
    }
  }
}