Fixing Node.js [ERR_MODULE_NOT_FOUND]: The Missing Extension Problem

beginner💚 Node.js2026-07-24| Node.js (v12.17.0+, v14.0.0+), Linux, macOS, Windows, ES Modules (type: module)

Error Message

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '...' imported from ...
#nodejs#es-modules#javascript#backend

The Error MessageIf you've recently switched a project to ES Modules, you’ve likely seen this wall of text in your terminal:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/path/to/project/utils' imported from /path/to/project/index.js
    at new NodeError (node:internal/errors:371:5)
    at finalizeResolution (node:internal/modules/esm/resolve:1018:11)
    at moduleResolve (node:internal/modules/esm/resolve:983:10)
    ... { 
  code: 'ERR_MODULE_NOT_FOUND'
}

Why This HappensThe shift from CommonJS (require) to ES Modules (import) changed the rules of the game. In the old CommonJS days, Node.js was helpful—perhaps too helpful. You could write const utils = require('./utils') and Node would automatically hunt for utils.js, utils.json, or even an index.js inside a folder.

ES Modules follow a stricter, browser-aligned specification. They require full specifiers. Node.js no longer guesses what file extension you want. If the file is utils.js, your code must say utils.js. This single missing dot and two letters account for roughly 90% of ESM migration headaches.

The Immediate Fix: Explicit ExtensionsTo clear the error, add the .js extension to all relative imports. It doesn't matter if you're importing a standard JavaScript file or a compiled asset; the extension is mandatory.

The Wrong Way```

// index.js import { helper } from './utils'; // Throws ERR_MODULE_NOT_FOUND


### The Right Way```
// index.js
import { helper } from './utils.js'; // Works perfectly

A quick note for TypeScript users: This feels wrong, but you must use the .js extension in your import statements even when the physical file on your disk is utils.ts. The TypeScript compiler (TSC) expects this behavior when targeting ESM, as it doesn't rewrite import paths during compilation.

The "Quick Fix" FlagUpdating a massive codebase with 500+ files isn't always feasible in one afternoon. If you're in a pinch, you can force Node.js to act like CommonJS using an experimental flag. Use this sparingly, as it's a band-aid, not a cure.

node --experimental-specifier-resolution=node index.js

This flag restores the legacy resolution algorithm, allowing you to omit extensions and directory indexes. Just keep in mind that this flag was removed in newer Node.js versions (like Node 19+) in favor of custom loaders.

Dealing with Directory ImportsIn the past, you could point to a folder and Node would find the index.js inside. ESM doesn't do that. You have to be specific.

CommonJS style (Fails)```

import { api } from './services';


### ESM style (Correct)```
import { api } from './services/index.js';

Configure VS Code to Do the WorkDon't manually type extensions every time. You can bake this requirement into your editor's auto-import logic. Open your .vscode/settings.json and drop in these lines:

{
  "javascript.preferences.importModuleSpecifierEnding": "js",
  "typescript.preferences.importModuleSpecifierEnding": "js"
}

Now, when you hit 'Enter' to auto-import a function, VS Code will automatically append the .js for you.

Double-Check Your WorkBefore pushing your code, run through this checklist:

  • Fire up the app with node index.js. If it starts without a stack trace, you're golden.- Scan your imports for relative paths starting with ./ or ../. Every single one needs an extension.- Ignore your node_modules. Imports like import express from 'express' don't need extensions because Node resolves packages differently than local files.## Automate Prevention with ESLintThe best way to stop this error is to make it a build failure. Use eslint-plugin-import to catch missing extensions during development. Add this rule to your config:
"rules": {
  "import/extensions": ["error", "always", { "js": "always", "mjs": "always" }]
}

With this rule active, your editor will underline any incomplete imports in red, preventing broken code from ever hitting your repository.

Related Error Notes