Skip to main content

module resolution

1. What is "module resolution"

Module resolution is the process by which Node.js finds the physical file (module) by the name specified in require() or import.

Put simply:

javascript
const express = require('express');

means:

Node.js has to find exactly where the express module is located: in the core, in node_modules, or on a local path.

2. The general search sequence

When Node.js encounters require('something'), it goes through five stages:

  1. Checks whether it is a built-in (core) module;
  2. Checks whether it is a path to a file (./, ../, /);
  3. Otherwise treats it as a module from node_modules;
  4. Looks for it in the nearest node_modules, walking up the directory tree;
  5. If not found, throws a MODULE_NOT_FOUND error.

3. Types of modules Node.js can resolve

TypeExampleDescription
Core (built-in)fs, path, os, httpBuilt into Node.js, no installation required
Local files./utils.js, ../config.jsonYour own files
Packages (npm)express, chalk, mongooseLooked up in node_modules
JSON / binaryconfig.json, addon.nodeParsed automatically or loaded as binary

4. The module resolution algorithm (for CommonJS)

Example:

javascript
const utils = require('./utils');

Node.js does the following:

  1. Checks whether './utils' is a path:
  • whether it starts with ./, ../, or /: yes.
  1. Converts the path to an absolute one:
javascript
/Users/tim/project/utils
  1. Tries the following variants (in order):
javascript
utils.js utils.json utils.node utils/index.js utils/index.json utils/index.node
  1. If nothing is found -> an error:
javascript
Error: Cannot find module './utils'

5. The algorithm for packages from node_modules

Example:

javascript
const express = require('express');

Node.js:

  1. Looks for a built-in module express -> no.
  2. Looks for node_modules/express in the current folder.
  3. If not found -> moves up the tree:
javascript
/Users/tim/project/node_modules/express /Users/tim/node_modules/express /Users/node_modules/express /node_modules/express
  1. Found it -> reads the file express/package.json.
  2. Looks for the "main" field in it:
javascript
{ "name": "express", "main": "index.js" }
  1. Loads /node_modules/express/index.js.

6. A visual example

javascript
project/ ├── app.js ├── utils/ │ └── index.js └── node_modules/ └── lodash/ ├── package.json └── lodash.js
javascript
require('./utils'); // → ./utils/index.js require('lodash'); // → ./node_modules/lodash/lodash.js require('fs'); // → a built-in Node.js module

7. How Node.js chooses between .js, .json, and .node

Node tries extensions in this order:

  1. .js - a regular JavaScript file;
  2. .json - automatically parsed into an object;
  3. .node - a binary native module (C++).

Example:

javascript
require('./config'); // will try config.js → config.json → config.node

8. Module resolution for ES Modules (ESM)

For ESM (import/export) the algorithm is different - stricter:

  • You must specify the exact extension (.js, .mjs, .json);
  • Folders with index.js are not searched for automatically;
  • __dirname and __filename are absent;
  • "exports" in package.json determines the available paths.

Example:

javascript
import utils from './utils/index.js'; // the extension is mandatory

9. How package.json affects module resolution

Node.js looks at the fields:

FieldPurpose
"main"The main entry point for CommonJS
"exports"A modern alternative for ESM / CJS
"type"Determines the module type (commonjs or module)

Example:

javascript
{ "name": "my-lib", "main": "./dist/index.cjs", "exports": { "import": "./dist/index.mjs", "require": "./dist/index.cjs" }, "type": "module" }

Node will use "exports" instead of "main"
if it is specified - this is the modern way to control which files can be imported from outside.

10. Caching during module resolution

Once Node.js has resolved a module and loaded it, the result is stored in require.cache:

javascript
console.log(require.cache);

A repeated require() does not execute the file again, it simply returns its exports from the cache.
This speeds up loading and prevents recursive cycles (circular dependencies).

11. Tools for debugging resolution

If you want to see exactly where a module is picked up from:

javascript
node -p "require.resolve('express')"

Example output:

javascript
/Users/tim/project/node_modules/express/index.js

Or for a local file:

javascript
node -p "require.resolve('./utils')"

In one sentence:

Module resolution is the mechanism by which Node.js determines where a module specified in require() or import is physically located, going through built-in modules, local files, and node_modules until it finds the right file.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.