Fix: Error Cannot find module in Node.js (MODULE_NOT_FOUND)

The Error

You run your Node.js app and get one of these:

Missing an npm package:

Error: Cannot find module 'express'
Require stack:
- /home/user/my-app/index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
    ...
    code: 'MODULE_NOT_FOUND'

Missing a local file:

Error: Cannot find module './utils/helpers'
Require stack:
- /home/user/my-app/src/index.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1075:15)
    ...
    code: 'MODULE_NOT_FOUND'

ESM variant (using import):

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/user/my-app/src/utils/helpers'
imported from /home/user/my-app/src/index.js

All of these have the same error code: MODULE_NOT_FOUND. The difference is whether Node.js can’t find a package (something in node_modules) or a local file (a relative or absolute path you wrote).

Why This Happens

Node.js resolves modules by searching specific locations in a specific order. When it can’t find a match anywhere, it throws MODULE_NOT_FOUND.

The most common causes:

  • The package isn’t installed. You’re requiring a package that isn’t in node_modules. Either you never ran npm install, or the package isn’t in your package.json.
  • node_modules is corrupted or incomplete. A failed install, a git merge, or manually deleting files can leave node_modules in a broken state.
  • The import path is wrong. A typo in the file path, a missing file extension, or confusion between relative and absolute paths.
  • A build step didn’t run. If you’re importing from a TypeScript file or a compiled output directory, the files might not exist yet because you haven’t run tsc or your build tool.
  • The main or exports field in package.json points to a file that doesn’t exist. This happens with packages that were partially published or have incorrect configuration.
  • You switched Node.js versions. Native modules compiled for one Node.js version won’t load on another. And if you used nvm use to switch versions, your globally installed packages are now in a different location.

Fix 1: Install the Missing Package

If the error names a package (not a relative path like ./something), install it:

npm install express

If it’s a dev dependency (testing libraries, TypeScript, build tools):

npm install --save-dev typescript

Check if the package is already listed in your package.json but just not installed:

npm ls express

If it’s listed but missing from node_modules, a plain npm install will fix it:

npm install

Common mistake: globally installed but not locally installed

If you installed a package globally (npm install -g some-cli) and then try to require() or import it in your project code, Node.js won’t find it. Global installs are for CLI tools you run from the terminal, not for packages you import in code. Install it locally:

npm install some-cli

Fix 2: Delete node_modules and Reinstall

If the package is in package.json but npm install isn’t fixing it, nuke node_modules and start fresh:

rm -rf node_modules package-lock.json
npm install

On Windows (Command Prompt):

rmdir /s /q node_modules
del package-lock.json
npm install

Deleting package-lock.json forces npm to resolve the entire dependency tree from scratch. This fixes issues caused by stale lockfile entries, partially installed packages, or corrupted symlinks. If the reinstall fails with permission errors, see Fix: EACCES permission denied when installing npm packages globally.

Fix 3: Fix the Import Path

If the error references a local file (e.g., Cannot find module './utils/helpers'), the issue is your import path.

Relative vs. absolute paths

Node.js treats paths starting with ./ or ../ as relative to the current file. Paths without a prefix are treated as packages in node_modules.

// Relative to current file -- looks for the file on disk
const helpers = require('./utils/helpers');

// Looks in node_modules -- this is NOT a file path
const express = require('express');

If you mean to import a local file, always start with ./ or ../:

// Wrong -- Node will look in node_modules for a package called "utils/helpers"
const helpers = require('utils/helpers');

// Correct -- relative path
const helpers = require('./utils/helpers');

File extensions

In CommonJS (require()), Node.js automatically tries .js, .json, and .node extensions. You can omit them:

// Both work in CJS
require('./helpers');    // finds ./helpers.js
require('./helpers.js'); // explicit

In ESM (import), file extensions are mandatory. Node.js will not guess:

// Wrong in ESM -- MODULE_NOT_FOUND
import { something } from './helpers';

// Correct in ESM
import { something } from './helpers.js';

This is a common source of ERR_MODULE_NOT_FOUND when migrating from CommonJS to ESM.

Index files

Node.js looks for index.js when you require a directory:

// Loads ./utils/index.js (CJS only)
const utils = require('./utils');

In ESM, this automatic index resolution does not work. You must be explicit:

// Wrong in ESM
import utils from './utils';

// Correct in ESM
import utils from './utils/index.js';

Case sensitivity

On macOS and Windows, the file system is case-insensitive by default. require('./Utils') will work even if the file is named utils.js. On Linux, it won’t. This causes errors that only appear in production (typically Linux) but never in local development.

Always match the exact casing of the file name.

Fix 4: Check package.json main and exports Fields

If you’re getting MODULE_NOT_FOUND when importing a package that is clearly installed in node_modules, the issue might be in that package’s package.json.

The main field

The main field tells Node.js which file to load when someone require()s the package:

{
  "name": "my-library",
  "main": "dist/index.js"
}

If dist/index.js doesn’t exist (because the package author forgot to run their build before publishing, or the files field in package.json excluded it), you’ll get MODULE_NOT_FOUND.

Check if the file actually exists:

ls node_modules/my-library/dist/index.js

If it’s missing, try reinstalling the package. If the file is still missing, the package is broken — check its GitHub issues or use a different version.

The exports field

Modern packages use exports to control which files can be imported. This is stricter than main:

{
  "name": "my-library",
  "exports": {
    ".": "./dist/index.js",
    "./utils": "./dist/utils.js"
  }
}

With this configuration:

// Works -- mapped by "."
import myLib from 'my-library';

// Works -- mapped by "./utils"
import utils from 'my-library/utils';

// Fails with MODULE_NOT_FOUND -- not in exports map
import internal from 'my-library/dist/internal.js';

If a package has an exports field, you can only import the paths it explicitly defines. Trying to reach into the package’s internals will throw MODULE_NOT_FOUND even though the file exists on disk.

Workaround: If you absolutely need to import an unexported file, some bundlers (like Webpack and Vite) let you bypass exports. In Node.js itself, there’s no clean way around it — this is by design.

Fix 5: TypeScript-Specific Issues

TypeScript adds its own layer of module resolution on top of Node.js. You can get MODULE_NOT_FOUND at runtime even when TypeScript compiles without errors.

moduleResolution

The moduleResolution setting in tsconfig.json controls how TypeScript looks up modules. The most common mistake is using the wrong value:

{
  "compilerOptions": {
    "moduleResolution": "node"
  }
}
  • "node" — Classic Node.js resolution. Works for CommonJS projects.
  • "node16" / "nodenext" — For projects using Node.js ESM ("type": "module" in package.json). Enforces file extensions in imports.
  • "bundler" — For projects using a bundler (Webpack, Vite, esbuild). More flexible, allows omitting extensions.

If your project uses ESM and you have "moduleResolution": "node", TypeScript may accept imports that Node.js will reject at runtime. Switch to "node16" or "nodenext".

paths and baseUrl

TypeScript’s paths option lets you create import aliases:

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*"]
    }
  }
}

This lets you write import { foo } from '@utils/helpers' in TypeScript. But Node.js doesn’t know about TypeScript’s paths. At runtime, Node.js sees @utils/helpers and looks for it in node_modules.

You need a runtime resolver to make this work:

  • ts-node / tsx: Use tsconfig-paths:
    npm install --save-dev tsconfig-paths
    node -r tsconfig-paths/register -r ts-node/register src/index.ts
  • Compiled output (tsc): Use tsc-alias to rewrite paths after compilation:
    npm install --save-dev tsc-alias
    tsc && tsc-alias
  • Bundlers (Webpack, Vite): Configure the bundler’s own alias resolution to match your paths.

Compiled output directory mismatch

If you compile TypeScript to a dist/ directory, make sure you’re running the compiled JavaScript, not the TypeScript source:

# Wrong -- Node.js can't run .ts files directly
node src/index.ts

# Correct -- run the compiled output
tsc
node dist/index.js

Also check that outDir in tsconfig.json matches where you’re looking for the files:

{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Edge Cases

ESM vs. CJS Interop

If your package.json has "type": "module", all .js files are treated as ESM. You can’t use require():

// Fails in ESM -- require is not defined
const express = require('express');

// Correct in ESM
import express from 'express';

Conversely, if you don’t have "type": "module", .js files are CommonJS. Static import statements will throw a syntax error:

// SyntaxError in CJS -- static import only works in ESM
import express from 'express';

// Correct in CJS
const express = require('express');

Some older packages only export CommonJS. If you’re in an ESM project and need to import a CJS-only package, use createRequire:

import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const someOldPackage = require('some-old-package');

Monorepo Hoisting

In monorepos using npm workspaces, Yarn, or pnpm, dependencies can be hoisted to the root node_modules. This means a package installed in one workspace can accidentally be importable from another workspace — until the dependency tree changes and it stops being hoisted.

If you’re getting MODULE_NOT_FOUND in a monorepo workspace:

  1. Make sure the package is listed in that workspace’s package.json, not just the root or another workspace.
  2. Run npm install (or your package manager’s install command) from the root.
  3. If using pnpm, note that pnpm does not hoist by default. Each package only sees its own dependencies. This is stricter but catches missing dependency declarations early.

Native Modules After Node.js Version Switch

If you switch Node.js versions (using nvm, fnm, or Volta) and get MODULE_NOT_FOUND for a native module (one that contains .node files), you need to rebuild:

npm rebuild

Or delete node_modules and reinstall:

rm -rf node_modules package-lock.json
npm install

Native modules are compiled against a specific Node.js ABI version. They won’t load on a different one.

Still Not Working?

Clear the npm cache

A corrupted cache can cause packages to install incorrectly:

npm cache clean --force
rm -rf node_modules package-lock.json
npm install

Check for .package-lock.json corruption

The file node_modules/.package-lock.json is npm’s internal cache of the installed dependency tree. If it gets out of sync with reality, npm may skip reinstalling packages it thinks are already there:

rm node_modules/.package-lock.json
npm install

Verify the file actually exists

This sounds obvious, but confirm the file you’re importing is where you think it is:

# Check if the package exists in node_modules
ls node_modules/express

# Check if your local file exists
ls src/utils/helpers.js

Check your NODE_PATH

If NODE_PATH is set in your environment, Node.js will also search those directories for modules. An incorrect NODE_PATH can cause confusing resolution behavior:

echo $NODE_PATH

Unless you have a specific reason to set it, clear it:

unset NODE_PATH

Check for circular dependencies

Circular imports (A imports B, B imports A) don’t throw MODULE_NOT_FOUND directly, but they can cause one of the modules to be partially loaded — which means exports you expect to exist are undefined, and error messages can be misleading. If you’re getting MODULE_NOT_FOUND on a file that definitely exists, check for circular imports using madge:

npx madge --circular src/

Related: If you’re getting dependency conflicts when installing packages, see Fix: npm ERR! ERESOLVE unable to resolve dependency tree.

Related Articles