Skip to content
Nilay Kabariya

Entry No.014·Fixes··2 min read

Fix: ERR_MODULE_NOT_FOUND Cannot find module in ES modules

Node can't find a file that is clearly there. What ES modules changed about relative imports, why TypeScript path aliases break at runtime, and what to change.

The error, verbatim

Jump to the fix ↓
node:internal/modules/esm/resolve:275
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'C:\project\src\utils' imported from C:\project\src\app.js
    at finalizeResolution (node:internal/modules/esm/resolve:275:11)
    at moduleResolve (node:internal/modules/esm/resolve:865:10)
    at defaultResolve (node:internal/modules/esm/resolve:991:11) {
  code: 'ERR_MODULE_NOT_FOUND'
}
✓ Reproduced on Windows 11 Pro

Tested on

Node
24.14.0 and 20.19.4
Module type
"type": "module"
TypeScript
5.9.3, module nodenext
OS
Windows 11 Pro
Contents
  1. The fix01
  2. Why it happens02
  3. The TypeScript path-alias version03
  4. What didn’t work04
  5. If you can’t face adding extensions everywhere05
  6. Run it yourself06
  7. How this was tested07

The fix

Write the file extension in every relative import:

import { add } from './utils.js';   // not './utils'

In TypeScript you still write .js, even though the file on disk is utils.ts. The extension you write is the one that exists after compiling:

import { hi } from './greet.js';    // greet.ts on disk

Importing a folder is a different error with the same cause, ERR_UNSUPPORTED_DIR_IMPORT. Point at the file:

import { mul } from './lib/index.js';   // not './lib'

Why it happens

CommonJS require() guessed for you. Given ./utils it tried utils.js, then utils.json, then utils/index.js. ES modules don’t guess: the import string is resolved as a URL, so ./utils means a file literally named utils, and when there’s none, resolution stops.

That’s why the message quotes a path with no extension and points inside node:internal/modules/esm/resolve rather than at anything you wrote. The file exists; the name you asked for doesn’t.

The TypeScript path-alias version

This one costs people the most time, because the code compiles cleanly and fails only when it runs.

{ "compilerOptions": { "paths": { "@/*": ["src/*"] } } }
import { hi } from '@/greet.js';

tsc accepts it. But paths only teaches the type checker where to look; it does not rewrite anything. The compiled file still says:

import { hi } from '@/greet.js';

and Node has never heard of @/:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'C:\project\dist\greet' imported from C:\project\dist\main.js

Two ways out, both verified on this project:

1. Node’s own aliases, which exist at runtime. Add an imports map to package.json (the keys must start with #):

{
  "imports": { "#lib/*": "./dist/*" },
  "type": "module"
}
import { hi } from '#lib/greet.js';

2. Keep @/ and run through a resolver that understands it. tsx reads paths from tsconfig.json:

npx tsx src/main.ts

What didn’t work

If you can’t face adding extensions everywhere

TypeScript will tell you exactly where they’re missing, with the corrected path, if you set "module": "nodenext":

error TS2835: Relative import paths need explicit file extensions in ECMAScript
imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './greet.js'?

That turns a runtime crash into a compile-time list you can work through once.

Run it yourself

The project I used is on GitHub, so you can trigger the error on your own machine in under a minute:

git clone https://github.com/nils44344/node-ts-error-repros
cd node-ts-error-repros/02-err-module-not-found
npm install && npm run repro

How this was tested

A small ES module project ("type": "module") with a relative import, a folder import and a TypeScript build using paths, run on Node 24.14.0 and Node 20.19.4 with TypeScript 5.9.3. Every error and every fix above is the real terminal output from that project.

— N.K., end of entry No.014

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.025

    Fix: TypeScript enum is not supported in strip-only mode

    Node's ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX for enum, parameter properties, namespace, export = and import =. Two ways to run it unchanged, both tested.

    > enum Color { Red, Green }

    FIXED3 min
  2. No.015

    Fix: This syntax is not allowed when 'erasableSyntaxOnly' is enabled

    TS1294: TypeScript rejects your enum, namespace or constructor shorthand. Why new projects turn this on, what each piece becomes, and when to switch it off.

    > src/a.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.

    FIXED3 min
  3. No.013

    Fix: Unknown file extension ".ts" (ERR_UNKNOWN_FILE_EXTENSION)

    Node refuses to run your TypeScript file. The version where this stops happening, what ts-node has to do with it, and the one-line way to run .ts today.

    > node:internal/modules/esm/get_format:189

    FIXED3 min

Post card · Newsletter

Get the next fix in your inbox.

One short email when a new entry is published. No spam, never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text