How Node.js Resolves Modules from Nested Folders

When a Node.js application imports a module from a deeply nested directory, the runtime employs a systematic lookup strategy to locate the file. This article explains how the Node.js dependency resolution algorithm traverses the directory tree, inspects node_modules folders, and resolves file extensions or package entry points to successfully load the requested dependency.


1. Categorizing the Import Path

Before searching the directory tree, Node.js categorizes the module identifier passed to require() or import. The resolution behavior depends on this classification:


2. The node_modules Directory Traversal

When resolving a package dependency from a nested folder, Node.js walks up the directory tree starting from the directory of the current file.

If a file located at /projects/app/src/components/buttons/submit.js calls require('lodash'), Node.js searches for the dependency in the following sequence:

  1. /projects/app/src/components/buttons/node_modules/lodash
  2. /projects/app/src/components/node_modules/lodash
  3. /projects/app/src/node_modules/lodash
  4. /projects/node_modules/lodash
  5. /node_modules/lodash

Node.js appends /node_modules to the current directory and checks for the package. If it is not found, Node.js moves up to the parent directory (using ..) and repeats the process. This bubbling continues until the algorithm reaches the root directory of the file system. If the module is still not found, Node.js checks globally configured paths (like the NODE_PATH environment variable) before throwing a MODULE_NOT_FOUND error.


3. Resolving the File Inside the Target Folder

Once Node.js locates the correct module folder (e.g., node_modules/lodash), it must determine which specific file to load. It processes the target directory using the following priority:

Step A: Check package.json

Node.js looks for a package.json file in the root of the target module folder. * Modern Resolution (Exports): If the package.json contains an "exports" field, Node.js uses it to map the import path to the correct internal file. * Legacy Resolution (Main): If "exports" is not present, Node.js looks for the "main" field (e.g., "main": "lib/index.js") and attempts to load the specified file.

Step B: Check for Default File Extensions

If there is no package.json, or if the "main" field is missing, Node.js attempts to match exact files in the target directory by appending common extensions in this order: 1. .js 2. .json 3. .node (for compiled binary addons)

Step C: Fallback to index

If no specific file is indicated by the package.json or found via direct extension matching, Node.js looks for a default index file inside the folder. It searches for: 1. index.js 2. index.json 3. index.node

If all of these steps fail to produce a file, Node.js moves to the next parent node_modules directory in the traversal chain.