What Happens When You Import Anything in Node.js?

Hassan BahatiHassan Bahati
5 min read

Ever wondered what really happens when you import a module in Node.js? How Node.js figures out where to find the file? Or why you sometimes get an error like:

To fully understand imports, we must first understand exports and how Node.js resolves modules. Let’s dive right in.

Exports — How modules define what can be imported

Originally, JavaScript code in Node.js was packaged using CommonJS modules.

Node.js considers each file as a separate module.

For instance, if you had a file called index.js with the following contents;

const articles = require("./article.js")

const text = "This is an article i wrote about the Node.js module resolution algorithm."
const title = "What Happens When You Import Anything in Node.js?"

console.log("The word count of the article is: ${article.wordCount(text)}")
console.log("The length of the title is ${article.titleLength(title)}")

First off index.js loads the module article.js that is located in a directory relative to itself. The contents of article.js are as below;

function wordCount(text) {
    return text.split(/\s+/).length;  // Splitting text into words and counting them
}

function titleLength(title) {
    return title.length;  // Returning the length of the title string
}

// Exporting the functions
exports.wordCount = wordCount;
exports.titleLength = titleLength;

The module article.js exports the functions wordCount() and titleLength().

But, Node.js also supports ECMAScript standard which is supported by web browsers and other JavaScript runtimes. According to Node.js Documentation, ECMAScript modules are the official standard format to package JavaScript code for reuse. Modules are defined using a variety of import and export statements.

A module needs to export something before it can be imported.

Node.js as well allows modules to explicitely define exports in package.json as seen below;

{
    "name": "my-package",
    "exports": {
            "./publications": "./article.js"
        }
}

In the above package.json file, the exports defines what is exposed by the module. This can be handy when one needs to prevent unintended access to internal files.

The functions from module article.js can now be imported as below;

import { wordCount, titleLength } from "my-package/publications"

const text = "This is an article i wrote about the Node.js module resolution algorithm."
const title = "What Happens When You Import Anything in Node.js?"

console.log("The word count of the article is: ${wordCount(text)}")
console.log("The length of the title is ${titleLength(title)}")

The Resolution Algorithm — How Node.js Resolves Imports

Node.js executes a strict procedure which is the resolution algorithm in order to check if some-module exists and if it does, where it is located in the file system. Several checks and tests are performed before a deduction is made.

Whenever you write;

import { anything } from "some-module"

The checks made are made for;

  • Core modules

  • Relative modules

  • NPM packages

  • Directories

  • Alias Modules

Check 1: Is It A Core Module?

Node.js core modules are a set of modules which you can use off-the-self without manual installation.

Some core modules include; fs, dns, events, os, path, url, util, stream, and so many more….

Node.js will first check if the import matches one of the inbuilt modules.

import fs from "fs" // Core Module

If its a core module, it will immediately get loaded.

Check 2: Is It A Relative Module?

Secondly, Node.js will check if the module name starts with /, ./, or ../. If it does, then the proceeding content will be treated as a file path and search for the module location with commence.

import { anything } from "/some-module"
import { something } from "./some-module"
import { things } from "../some-module"

If the module name starts with /, a search at the root of the file system will be made.

If the module name starts with ./ or ../ , a search will be made relative to the location of the current file.

Check 3: Is It An NPM Package?

This check search through node_modules.

If a module is not a core module and neither a relative module, Node.js will assume it a package installed from npm registry and will commence a search through the node_modules directory.

import { useDocumentQuery } from "@tanstack-query-firebase/react" // Searches node_modules for @tanstack-query-firebase

Node.js will check if node_modules/@tanstack-query-firebase/react/package.json exists.

If the node_modules/@tanstack-query-firebase/react/package.json exists, a check if there exists an exports field in the package.json . If exists, what is exposed by the export will be resolved.

{
    "name": "my-package",
    "exports": {
            "./publications": "./article.js"
        }
}

If the node_modules/@tanstack-query-firebase/react/package.json exists, and doesn’t have an exports field in the package.json. The main field will them be considered and its value will be resolved.

{
    "name": "my-package",
    "main": "./article.js"
}

In the event that a main field doesn’t exist in the package.json, an available index.js or index.mjs file will be considered.

If @tanstack-query-firebase is not found at the current directory level, Node.js will move subsequent levels upwards the file system while checking for it.

 ./node_modules/@tanstack-query-firebase  
../node_modules/@tanstack-query-firebase  
../../node_modules/@tanstack-query-firebase

Packages installed at the root of your file system are ideally be available to all Node.js apps on your system.

Check 4: Is It A Directory?

If the import refers to a directory, Node.js will search for a package.json file in that directory. In the package.json, a main field as an entry will be looked out for.

import utils from "./utils"

If a package.json doesn’t exist, an index.js or index.mjs will be searched.

./utils/package.json (looks for "main" field)  
./utils/index.js  
./utils/index.mjs

Check 5: Additional Checks and Considerations

Additionally, Node.js will check for an ESM vs CommonJS Compatibility.

If you import an ESM (.mjs) module into a CommonJS (.cjs) file, or vice versa, Node.js may throw an error.

// CommonJS (require)
const package = require('some-esm-module'); // Error if it's an ESM package

To avoid conflicts, ensure your package.json has:

{
  "type": "module"
}

or explicitly use .mjs or .cjs extensions.

Conclusion

The Node.js Resolution Algorithm gives insight into what happens under the hood, every time we import anything in Node.js.

References

https://nodejs.org/api/packages.html#exports

https://nodejs.org/api/modules.html#modules-commonjs-modules

https://nodejs.org/api/esm.html#modules-ecmascript-modules

4
Subscribe to my newsletter

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

Written by

Hassan Bahati
Hassan Bahati