Node.js Import Aliases
Tags: node, javascript, guides, tooling
Published: 2022-07-05 13:01:14

---
title: Node.js Import Aliases
published: true
date: 2022-07-05 11:39:43 UTC
tags: nodejs, javascript, guides, tooling
canonical_url: https://abhijithota.me/posts/node-import-aliases/
---

> **Originally published on [abhijithota.me](https://abhijithota.me/posts/node-import-aliases/)**

## The Problem

Oftentimes, as a Node.js codebase grows, this happens:

```js
import { UserModel } from "../../../../db/models/index.js";
import { validate } from "../../../../lib/utils.js";
import { SERVICE_API_KEY } from "../../../../lib/constants.js";
```

There are a few problems with this:

- **Sensitivity to folder structure changes**: A good IDE or editor can auto-import but not all of them are without errors. Also, what if you change something outside your general IDE?
- **Clutter**: It just simply looks bad

## The Solution

A new field in `package.json` called `imports` was **stabilized in Node.js v14**. It was introduced earlier in Node.js v12. It follows certain rules and lets you "map" certain aliases (custom paths) to a path of your choice and also declare fallbacks.

[Here's the documentation](https://nodejs.org/docs/latest-v14.x/api/packages.html#packages_imports) for the same.

We can solve our example problem by adding this to our `package.json`:

```json
"imports": {
  "#models": "./src/db/models/index.js",
  "#utils": "./src/lib/utils.js",
  "#constants": "./src/lib/constants.js"
}
```

and use them in your code anywhere like this:

```js
import { UserModel } from "#models";
import { Validate } from "#utils";
import { SERVICE_API_KEY } from "#constants";
```

### Note
 
- The entries in the `imports` field of `package.json` must be strings starting with `#` to ensure they are disambiguated from package specifiers like `@`.
- The values should be relative paths from the root of the project. The root is where your `package.json` is.
  
  In the above example, we assumed `package.json` was at the root and all the relevant files were inside a `src` directory.

You should see your application run fine but your IDE of choice may show some errors. Undesirable red and yellow squiggles are no one's favorite. It would also auto-import from the actual relative path instead of the path alias. That's no fun.

**`jsconfig.json` to the rescue**. (`tsconfig.json` if you're in a TypeScript project.)

In your `jsconfig.json`, add the following

```json
"compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "#models": ["./src/db/models/index.js"],
      "#utils": ["./src/lib/utils.js"],
      "#constants": ["./src/lib/constants.js"]
    }
}
```

> The above configuration tells your IDE's LSP to look for code in the given prefixes. [Refer to the documentation](https://www.typescriptlang.org/tsconfig#paths) of the property to know more.

Now we have sweet auto-imports from the desired location:

![Screenshot of auto-import working desirably](./2022-07-05-16-45-49.png)

## Fallback dependencies

As seen in the [documentation](https://nodejs.org/docs/latest-v14.x/api/packages.html#packages_subpath_imports), you can also use this property for conditionally setting up fallback packages or polyfills. From the documentation:

> ```json
> // package.json
> {
>   "imports": {
>     "#dep": {
>       "node": "dep-node-native",
>       "default": "./dep-polyfill.js"
>     }
>   },
>   "dependencies": {
>     "dep-node-native": "^1.0.0"
>   }
> }
> ```
> [Here, if the] import `#dep` does not get the resolution of the external package `dep-node-native` (including its exports in turn), and instead gets the local file `./dep-polyfill.js` relative to the package in other environments.

## Frontend projects

I haven't tried this approach with frontend applications. They generally use a bundling system like Webpack or Rollup which have their own way of resolving aliases. For example, for [Vite](https://vitejs.dev/) (which uses Rollup and ESBuild), you should add this to your `vite.config.js`:

```js
import path from "path";

export default defineConfig({
//   Some other config
	resolve: {
		alias: {
			"#": path.resolve(__dirname, "./src"),
		},
	},
});
```

and in your `jsconfig.json`:

```json
{
	"compilerOptions": {
		"baseUrl": ".",
		"paths": {
			"#/*": ["src/*"]
		}
	}
}
```

The above configuration maps everything starting with `#` to immediate folders and files below `src`. YMMV.


> *A designer knows he has arrived at perfection not when there is no longer anything to add but when there is no longer anything to take away*
> 
> \- Jon Bentley in Programming Pearls