Cannot Find Module Error in Node.js: Complete Fix Guide voor Backend Developers
De foutmelding Error: Cannot find module '...' is één van de meest voorkomende (en frustrerende) problemen in Node.js backends. Het lijkt simpel—“module niet gevonden”—maar de oorzaken lopen uiteen: van een ontbrekende dependency, een fout pad, een mismatch tussen CommonJS en ESM, tot monorepo-resolutieproblemen, TypeScript build outputs, of zelfs een verkeerd ingestelde package.json met exports.
Deze gids is bedoeld als een complete, praktische fix guide voor backend developers. Je krijgt diepgaande uitleg, concrete diagnose-stappen, en realistische commando’s die je direct kunt uitvoeren.
Inhoudsopgave
- Wat betekent “Cannot find module” precies?
- Lees de stacktrace als een pro
- Snelle checklist (80% van de cases)
- Dependency ontbreekt of staat verkeerd in package.json
- Node.js module resolution uitgelegd (CJS)
- ES Modules (ESM) vs CommonJS: veelgemaakte fouten
- TypeScript: build output en runtime paden
- Path issues: relative vs absolute imports, case sensitivity
- package.json
exports,main,module: valkuilen - Monorepo’s (npm/pnpm/yarn workspaces): hoisting en resolutie
- Docker & CI: “works on my machine” voorkomen
- Native modules, platform issues en Node-versies
- Geavanceerde debugging: Node flags en resolutie inspecteren
- Praktische fixes per scenario
- Preventie: best practices voor stabiele builds
Wat betekent “Cannot find module” precies?
Wanneer Node.js een require() (CommonJS) of import (ESM) uitvoert, probeert het een module te resolven naar een fysiek bestand of package-entrypoint. Als dat niet lukt, krijg je:
- CommonJS:
Error: Cannot find module 'express' - ESM (varianten):
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'express' imported from /app/index.mjs
Belangrijk: “module” kan betekenen:
- Een npm package (bijv.
express,lodash) - Een lokaal bestand (bijv.
./utils/logger.js) - Een subpath binnen een package (bijv.
date-fns/format) - Een Node core module (bijv.
fs,path) — deze zou vrijwel nooit “not found” moeten geven, tenzij er iets heel vreemds gebeurt (zoals een bundler/loader misconfiguratie of shadowing).
Lees de stacktrace als een pro
De stacktrace vertelt je waar Node probeerde te resolven en wat het precies zocht.
Voorbeeld:
Error: Cannot find module './routes/user'
Require stack:
- /app/src/server.js
- /app/src/index.js
Interpretatie:
- De module die niet gevonden wordt is
./routes/user(relatief pad). - Node kwam daar terecht via
server.js, aangeroepen doorindex.js. - Je moet dus in
/app/src/server.jskijken naar derequire('./routes/user').
Bij ESM zie je vaak:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/src/routes/user' imported from /app/src/server.js
Hier zie je soms een absoluut pad omdat ESM resolutie en error formatting anders is.
Snelle checklist (80% van de cases)
Voer deze stappen uit vóór je diep gaat graven:
- Bestaat de dependency in
package.json? - Is
node_modulesaanwezig? - Heb je
npm install/pnpm install/yarn installgedraaid? - Gebruik je de juiste Node-versie?
- Is het pad correct (en met juiste hoofdletters)?
- Draai je code vanuit de juiste working directory?
- Gebruik je ESM syntax met een CommonJS project (of andersom)?
- In Docker/CI: is
node_modulesmeegebouwd en niet overschreven door volumes?
Snelle commando’s:
node -v
npm -v
pwd
ls -la
cat package.json
npm ls --depth=0
Dependency ontbreekt of staat verkeerd in package.json
Symptoom
Error: Cannot find module 'express'
Diagnose
Check of express in dependencies staat:
cat package.json | sed -n '1,120p'
Of:
node -p "require('./package.json').dependencies && require('./package.json').dependencies.express"
Check of het geïnstalleerd is:
npm ls express
Fix
Installeer het:
npm install express
Of als het alleen voor development nodig is (bijv. nodemon, typescript):
npm install -D nodemon
Belangrijk voor backend deploys: packages die je in productie nodig hebt moeten in "dependencies" staan, niet in "devDependencies". Veel CI/CD pipelines doen:
npm ci --omit=dev
Als jouw server runtime dan iets uit devDependencies nodig heeft, krijg je “Cannot find module”.
Fix: verplaats dependency:
npm uninstall some-package
npm install some-package --save
Node.js module resolution uitgelegd (CJS)
Voor CommonJS (require) werkt Node grofweg zo:
1) Core modules
require('fs') → direct beschikbaar.
2) File/relative paths
require('./utils'):
- probeert
./utils - dan
./utils.js - dan
./utils.json - dan
./utils.node - dan
./utils/index.js(en varianten)
3) Packages in node_modules
require('express'):
- zoekt in
node_modules/express - leest
node_modules/express/package.json - gebruikt
main(of defaultindex.js)
4) Parent directories
Node zoekt node_modules omhoog in de directory tree:
/app/src/node_modules/app/node_modules/node_modules
Daarom kan een dependency “toevallig” werken in een monorepo of op je laptop, maar niet in een clean container.
ES Modules (ESM) vs CommonJS: veelgemaakte fouten
Node ondersteunt twee module-systemen:
- CommonJS (CJS):
require,module.exports - ESM:
import,export
Hoe weet Node wat je gebruikt?
- Als
package.json"type": "module"→.jsis ESM. - Zonder
"type": "module"→.jsis CJS. .mjsis altijd ESM..cjsis altijd CJS.
Veelvoorkomende fout 1: import in CJS project
import express from 'express';
In een CJS project geeft dit vaak syntax errors, maar afhankelijk van tooling kan het ook in module-not-found situaties eindigen (bijv. transpilers/loader mismatch).
Fix opties:
- Zet
"type": "module"inpackage.json(als je echt ESM wilt), of - Gebruik CJS syntax:
const express = require('express');
Veelvoorkomende fout 2: require van ESM-only package
Steeds meer packages zijn ESM-only. Dan krijg je soms niet “Cannot find module”, maar errors zoals ERR_REQUIRE_ESM. Toch kan het in sommige setups lijken op module-resolutie issues.
Fix: gebruik import (ESM), of dynamische import in CJS:
(async () => {
const { default: somePkg } = await import('some-esm-only-package');
})();
Veelvoorkomende fout 3: ESM vereist expliciete extensies
In ESM moet je vaak expliciet .js toevoegen bij lokale imports:
// fout in ESM:
import { logger } from './logger';
// correct:
import { logger } from './logger.js';
Als je dit vergeet, krijg je vaak:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/src/logger' imported from ...
Fix: voeg extensies toe, of gebruik een build step/bundler die dit voor je oplost (maar wees consistent).
TypeScript: build output en runtime paden
TypeScript introduceert een extra laag: je schrijft .ts, maar Node draait meestal .js in dist/.
Klassieke oorzaak
Je start per ongeluk de TS broncode met Node:
node src/index.ts
Node kan geen .ts importeren zonder loader, en afhankelijk van de error kan dit eindigen in “module not found” (zeker als TS path aliases gebruikt).
Correct: build en run dist:
npx tsc
node dist/index.js
TS path aliases (tsconfig paths) werken niet in Node runtime
In tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@src/*": ["src/*"]
}
}
}
In code:
import { foo } from '@src/foo';
TypeScript compileert dit vaak niet naar een relatief pad; Node begrijpt @src/foo niet → runtime error:
Cannot find module '@src/foo'
Fix opties:
- Gebruik relatieve imports (simpel, maar soms minder mooi).
- Gebruik een runtime resolver zoals
tsconfig-paths(bij ts-node) of bundling. - In ESM/NodeNext setups: gebruik
importsinpackage.json(gecontroleerd).
Voorbeeld met ts-node + tsconfig-paths:
npm install -D ts-node tsconfig-paths
node -r ts-node/register -r tsconfig-paths/register src/index.ts
Voor productie: liever prebuild en geen ts-node.
Path issues: relative vs absolute imports, case sensitivity
Case sensitivity (Linux vs macOS/Windows)
Op macOS/Windows is het filesystem vaak case-insensitive. Op Linux (Docker/servers) is het case-sensitive.
Je hebt:
require('./Routes/user');
Maar map heet routes. Lokaal werkt het, in Docker niet:
Cannot find module './Routes/user'
Fix: corrigeer casing en commit de juiste bestandsnaam.
Handige check:
git status --porcelain
git ls-files | grep -i routes
Working directory (process.cwd) mismatch
Als je code iets doet als:
require(process.cwd() + '/config/default.json');
En je start de app vanuit een andere directory, faalt het.
Fix: gebruik __dirname (CJS) of import.meta.url (ESM).
CJS:
const path = require('path');
const configPath = path.join(__dirname, 'config', 'default.json');
ESM:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, 'config', 'default.json');
package.json exports, main, module: valkuilen
main
Voor CJS bepaalt main het entrypoint:
{
"main": "dist/index.js"
}
Als dist/index.js niet bestaat (build niet gedraaid), krijg je:
Cannot find module .../dist/index.js
Fix: build stap toevoegen en artifacts meenemen.
exports (subpath exports)
exports kan imports beperken:
{
"name": "my-lib",
"exports": {
".": "./dist/index.js"
}
}
Als je consumer probeert:
import x from 'my-lib/internal';
Dan faalt het, soms met “Cannot find module” of “Package subpath not defined”.
Fix: exporteer expliciet:
{
"exports": {
".": "./dist/index.js",
"./internal": "./dist/internal.js"
}
}
type: module
Als je "type": "module" zet, maar je build output is CJS (bijv. module.exports), dan krijg je vreemde runtime errors. Soms lijkt het op resolutieproblemen.
Fix: align TS/Build config met package type.
Monorepo’s (npm/pnpm/yarn workspaces): hoisting en resolutie
In workspaces kunnen dependencies “gehoist” worden naar de root node_modules. Dat kan lokaal werken, maar in productie (of in een package geïsoleerd) falen.
Symptoom
In packages/api:
Cannot find module 'pg'
Maar pg staat alleen in root dependencies, niet in packages/api/package.json.
Fix: declareer dependencies per package:
cd packages/api
npm install pg
Of met pnpm:
pnpm --filter api add pg
pnpm strictness
pnpm is strikter: je kunt niet “per ongeluk” dependencies gebruiken die je niet declared hebt. Dat is goed, maar kan plots errors geven als je van npm naar pnpm migreert.
Diagnose:
pnpm why pg
pnpm -r list --depth=0
Docker & CI: “works on my machine” voorkomen
Docker is een top bron van “Cannot find module”, vooral door:
node_modulesniet geïnstalleerd in imagenode_modulesoverschreven door volume mountsnpm ci --omit=devterwijl je devDependencies runtime nodig hebt- build artifacts (
dist/) niet gekopieerd
Correcte Dockerfile (voorbeeld voor build + runtime)
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]
Volume mount valkuil (docker-compose)
Als je dit doet:
volumes:
- .:/app
Dan mount je je lokale project over /app heen, en als je lokaal geen node_modules in de container context hebt, “verdwijnt” de geïnstalleerde node_modules van de image.
Fix: mount node_modules als named volume of installeer in runtime container.
Voorbeeld aanpak:
- Mount code
- Houd
node_modulesin container:
volumes:
- .:/app
- /app/node_modules
Native modules, platform issues en Node-versies
Sommige packages bevatten native bindings (bcrypt, sharp, sqlite3, etc.). Als install/build faalt, kan de module ontbreken.
Symptoom
- Install errors tijdens
npm install - Of runtime:
Cannot find module 'bcrypt'
Maar het staat wel in package.json.
Diagnose
Check install logs:
npm ci --verbose
Rebuild native modules:
npm rebuild
Of specifiek:
npm rebuild bcrypt --build-from-source
Check Node ABI mismatch (bijv. Node 18 vs 20):
node -p "process.versions"
Fix: pin Node versie in .nvmrc en in Docker image, en rebuild dependencies.
Geavanceerde debugging: Node flags en resolutie inspecteren
1) require.resolve gebruiken
In CJS:
node -e "console.log(require.resolve('express'))"
Voor een lokaal pad:
node -e "console.log(require.resolve('./src/server.js'))"
Als dit faalt, weet je dat resolutie het probleem is, los van je app.
2) Print module search paths
node -e "console.log(module.paths)"
Dit laat zien waar Node zoekt naar node_modules.
3) ESM resolutie debuggen
Voor ESM kun je minder direct require.resolve gebruiken, maar je kunt wel testen met een klein .mjs script:
cat > resolve-test.mjs <<'EOF'
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
console.log(require.resolve('express'));
EOF
node resolve-test.mjs
4) NODE_DEBUG (beperkt maar soms nuttig)
Sommige Node subsystemen loggen extra info:
NODE_DEBUG=module node src/index.js
Dit kan veel output geven, maar je ziet soms precies welke paden geprobeerd worden.
Praktische fixes per scenario
Hieronder een set “als je X ziet, doe Y” oplossingen.
Scenario A: Package niet geïnstalleerd
Error:
Cannot find module 'dotenv'
Fix:
npm install dotenv
En check:
npm ls dotenv
Scenario B: Dependency staat in devDependencies maar is nodig in productie
Fix:
npm install some-runtime-package --save
npm prune --omit=dev
Test productie-install lokaal:
rm -rf node_modules
npm ci --omit=dev
node dist/index.js
Scenario C: ESM import zonder extensie
Error:
ERR_MODULE_NOT_FOUND: Cannot find module '/app/dist/logger'
Fix in code:
import { logger } from './logger.js';
Scenario D: TypeScript path alias werkt niet in runtime
Fix 1 (aanrader): compileer naar relatieve paden (of vermijd alias in runtime)
Fix 2: bundlen (esbuild/tsup) zodat aliases verdwijnen
Voorbeeld met esbuild:
npm install -D esbuild
npx esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js
node dist/index.js
Scenario E: Docker overschrijft node_modules
Fix: pas docker-compose volumes aan:
services:
api:
build: .
volumes:
- .:/app
- /app/node_modules
Scenario F: main/exports wijst naar niet-bestaand bestand
Fix: build stap en correcte paden.
Check:
cat package.json
ls -la dist
Preventie: best practices voor stabiele builds
1) Pin Node versie
-
.nvmrc:20 -
package.json engines:
{ "engines": { "node": ">=20 <21" } }
2) Gebruik npm ci in CI
npm ci is deterministisch en gebruikt package-lock.json.
npm ci
npm test
npm run build
3) Test productie-install lokaal
Simuleer productie:
rm -rf node_modules
npm ci --omit=dev
node dist/index.js
4) Vermijd “magische” imports
- Geen
process.cwd()-paden zonder reden - Wees consistent met ESM/CJS
- In ESM: altijd expliciete extensies voor lokale files
5) Monorepo discipline
Elke package declareert z’n eigen runtime dependencies. Voeg tooling toe om dit te checken (bijv. lint rules of dependency-cruiser), maar de basis is: niet leunen op hoisting.
Samenvatting: zo los je “Cannot find module” systematisch op
- Lees de stacktrace: welke import faalt, vanuit welk bestand?
- Bepaal type import: package vs lokaal pad.
- Check installatie:
npm ls <pkg>en ofnode_modulesbestaat. - Check environment: Node versie, Docker volumes, CI flags (
--omit=dev). - Check module-systeem: ESM vs CJS, extensies,
type. - Check build artifacts: bestaat
dist/en kloptmain/exports? - Gebruik resolutie-tools:
require.resolve,NODE_DEBUG=module.
Als je wilt, kun je de exacte error + relevante bestanden (package.json, importregel, directory-structuur) plakken; dan kan ik gericht aangeven welke oorzaak het meest waarschijnlijk is en welke fix het schoonst is voor jouw setup (CJS/ESM, TS, Docker, monorepo).