Fix: Node.js ERR_MODULE_NOT_FOUND - Cannot find module
Quick Answer
How to fix Node.js ERR_MODULE_NOT_FOUND when using ES modules, covering missing file extensions, directory imports, package.json exports, TypeScript paths, and ESM resolution differences.
The Error
You run a Node.js script using ES modules and get:
node:internal/errors:496
ErrorCaptureStackTrace(err);
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/project/src/utils' imported from /project/src/index.js
at finalizeResolution (node:internal/modules/esm/resolve:260:11)Or a variation like:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/project/src/utils/index.js'
imported from /project/src/app.js
Did you mean to import "../utils/index.js"?This error is specific to ES modules (ESM). It looks similar to the classic MODULE_NOT_FOUND error from CommonJS, but it has different rules and different fixes. If you are using require() instead of import, see Fix: Cannot find module (CommonJS).
Why This Happens
Node.js has two module systems: CommonJS (CJS) and ES Modules (ESM). They resolve imports differently.
In CommonJS (require):
- File extensions are optional:
require('./utils')triesutils.js,utils.json,utils/index.js - Directory imports work:
require('./utils')resolves toutils/index.js
In ES Modules (import):
- File extensions are mandatory:
import './utils'does not try.jsautomatically - Directory imports do not work:
import './utils'does not resolve toutils/index.js - Package
exportsfield inpackage.jsontakes precedence over file system lookups
These stricter rules are by design — ESM follows the browser’s module resolution behavior, where URLs must include the full path. But they catch many developers off guard when migrating from CommonJS.
Fix 1: Add File Extensions to Imports
The most common fix. ESM requires explicit file extensions:
Broken:
import { helper } from './utils';
import { db } from '../lib/database';Fixed:
import { helper } from './utils.js';
import { db } from '../lib/database.js';Yes, even if the source file is .ts, the import must use .js when TypeScript compiles to JavaScript. TypeScript does not rewrite import paths during compilation.
This applies to all relative imports. Package imports (import express from 'express') do not need extensions — Node.js resolves those through node_modules.
Pro Tip: Use a linting rule to enforce file extensions in imports. The ESLint rule
import/extensionscan catch missing extensions at development time, before you hit the runtime error.
Fix 2: Fix Directory Imports
In CommonJS, require('./utils') automatically resolves to ./utils/index.js. In ESM, this does not work:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/project/src/utils'Fix: Import the index file explicitly:
import { helper } from './utils/index.js';Or create a package.json inside the directory with an exports field:
{
"exports": "./index.js"
}Then the bare directory import works:
import { helper } from './utils';Fix 3: Configure package.json exports
The exports field in package.json controls how your package’s modules are resolved. If it is misconfigured, imports fail with ERR_MODULE_NOT_FOUND.
A typical exports configuration:
{
"name": "my-package",
"type": "module",
"exports": {
".": "./src/index.js",
"./utils": "./src/utils/index.js",
"./helpers/*": "./src/helpers/*.js"
}
}This allows:
import { main } from 'my-package'; // resolves to ./src/index.js
import { helper } from 'my-package/utils'; // resolves to ./src/utils/index.js
import { foo } from 'my-package/helpers/foo'; // resolves to ./src/helpers/foo.jsCommon issues:
- Missing the
"."entry — the main entry point is not exported - Forgetting to include subpath patterns —
import 'my-package/utils'fails - File paths in
exportsdon’t match actual file locations
If the exports field exists, it completely overrides the main and module fields. Node.js ignores those when exports is present.
If module resolution errors happen during bundling rather than Node.js runtime, see Fix: Module not found: Can’t resolve for webpack/Vite-specific solutions.
Fix 4: Fix TypeScript Output Paths
TypeScript compiles .ts files to .js files, but it does not rewrite import paths. This means:
// src/index.ts
import { helper } from './utils'; // Works in TypeScript...Compiles to:
// dist/index.js
import { helper } from './utils'; // ...but fails in Node.js ESMFix option 1: Use .js extensions in TypeScript source files:
import { helper } from './utils.js'; // Yes, .js even in .ts filesThis looks strange but is the recommended approach. TypeScript understands that ./utils.js refers to ./utils.ts during compilation.
Fix option 2: Use a build tool that rewrites paths:
Tools like tsup, unbuild, or esbuild can bundle your TypeScript and handle path resolution:
npx tsup src/index.ts --format esmFix option 3: Use tsx to run TypeScript directly:
npx tsx src/index.tstsx handles ESM resolution automatically, including file extensions and directory imports. It is ideal for development and scripts.
If TypeScript can’t find your modules at all, see Fix: TypeScript Cannot find module.
Fix 5: Replace __dirname and __filename
In CommonJS, __dirname and __filename are global variables. In ESM, they do not exist:
console.log(__dirname);
// ReferenceError: __dirname is not defined in ES module scopeFix: Use import.meta.url:
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Now you can use __dirname as before
const configPath = join(__dirname, 'config.json');In Node.js 21.2+, you can use import.meta.dirname and import.meta.filename directly:
const configPath = join(import.meta.dirname, 'config.json');Fix 6: Handle JSON Imports
ESM does not import JSON files by default:
import config from './config.json';
// TypeError [ERR_IMPORT_ATTRIBUTE_MISSING]: Module needs an import attribute of "type: json"Fix: Add the import attribute (Node.js 20.10+):
import config from './config.json' with { type: 'json' };For older Node.js versions, use assert instead of with:
import config from './config.json' assert { type: 'json' };Or read the JSON file manually:
import { readFileSync } from 'node:fs';
const config = JSON.parse(readFileSync(new URL('./config.json', import.meta.url), 'utf-8'));Fix 7: Fix node_modules Package Resolution
If the error points to a package in node_modules:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../node_modules/some-package/lib/index.mjs'The package might have a broken exports field or missing files.
Step 1: Reinstall the package:
rm -rf node_modules package-lock.json
npm installStep 2: Check if the package supports ESM:
npm info some-packageLook for "type": "module" or an exports field in the package’s package.json. Some older packages only support CommonJS.
Step 3: Use a dynamic import for CJS-only packages:
const pkg = await import('cjs-only-package');Dynamic import() works with both ESM and CJS packages.
If you’re getting a similar error but with import syntax being rejected entirely, see Fix: Cannot use import statement outside a module.
Fix 8: Check for Case Sensitivity
On Linux, file paths are case-sensitive. import './Utils.js' and import './utils.js' are different:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/project/src/Utils.js'Check the actual filename:
ls src/Match the import exactly to the file’s case. This is a common issue when developing on macOS (case-insensitive) and deploying to Linux (case-sensitive).
If you’re hitting module resolution issues in Vite or webpack, see Fix: Vite failed to resolve import.
Still Not Working?
If none of the fixes above resolved the error:
Check your package.json has "type": "module". Without this, Node.js treats .js files as CommonJS, and import syntax fails with a different error. Add it:
{
"type": "module"
}Check your Node.js version. Full ESM support requires Node.js 14+ (with --experimental-modules flag) or Node.js 16+ (stable). Update to the latest LTS version:
nvm install --ltsCheck for symlink issues. npm link and pnpm use symlinks. ESM resolves paths differently with symlinks. Try --preserve-symlinks:
node --preserve-symlinks index.jsCheck for import map issues. If you use --import or custom loaders, they can interfere with module resolution. Try running without them first.
Use --trace-warnings for more detail:
node --trace-warnings index.jsThis shows the full stack trace for resolution errors, which helps pinpoint exactly which import is failing.
Check the exact error code. ERR_MODULE_NOT_FOUND means the file path doesn’t exist. The related ERR_PACKAGE_PATH_NOT_EXPORTED means the file exists but isn’t listed in the package’s exports field — a different fix entirely.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: SyntaxError: Cannot use import statement outside a module
How to fix 'SyntaxError: Cannot use import statement outside a module' in Node.js, TypeScript, Jest, and browsers by configuring ESM, package.json type, and transpiler settings.
Fix: Express Cannot GET /route (404 Not Found)
How to fix Express.js Cannot GET route 404 error caused by wrong route paths, missing middleware, route order issues, static files, and router mounting problems.
Fix: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
How to fix the JavaScript heap out of memory error by increasing Node.js memory limits, fixing memory leaks, and optimizing builds in webpack, Vite, and Docker.
Fix: npm ERR! enoent ENOENT: no such file or directory
How to fix the npm ENOENT no such file or directory error caused by missing package.json, wrong directory, corrupted node_modules, broken symlinks, and npm cache issues.