← Terug naar tutorials

Cannot Find Module Error in Node.js: Complete Fix Guide voor Backend Developers

node.jsbackendjavascripttypescriptdebuggingdependenciesnpmesmcommonjsmonorepo

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

  1. Wat betekent “Cannot find module” precies?
  2. Lees de stacktrace als een pro
  3. Snelle checklist (80% van de cases)
  4. Dependency ontbreekt of staat verkeerd in package.json
  5. Node.js module resolution uitgelegd (CJS)
  6. ES Modules (ESM) vs CommonJS: veelgemaakte fouten
  7. TypeScript: build output en runtime paden
  8. Path issues: relative vs absolute imports, case sensitivity
  9. package.json exports, main, module: valkuilen
  10. Monorepo’s (npm/pnpm/yarn workspaces): hoisting en resolutie
  11. Docker & CI: “works on my machine” voorkomen
  12. Native modules, platform issues en Node-versies
  13. Geavanceerde debugging: Node flags en resolutie inspecteren
  14. Praktische fixes per scenario
  15. 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:

Belangrijk: “module” kan betekenen:

  1. Een npm package (bijv. express, lodash)
  2. Een lokaal bestand (bijv. ./utils/logger.js)
  3. Een subpath binnen een package (bijv. date-fns/format)
  4. 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:

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:

  1. Bestaat de dependency in package.json?
  2. Is node_modules aanwezig?
  3. Heb je npm install / pnpm install / yarn install gedraaid?
  4. Gebruik je de juiste Node-versie?
  5. Is het pad correct (en met juiste hoofdletters)?
  6. Draai je code vanuit de juiste working directory?
  7. Gebruik je ESM syntax met een CommonJS project (of andersom)?
  8. In Docker/CI: is node_modules meegebouwd 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'):

3) Packages in node_modules

require('express'):

4) Parent directories

Node zoekt node_modules omhoog in de directory tree:

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:

Hoe weet Node wat je gebruikt?

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:

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:

  1. Gebruik relatieve imports (simpel, maar soms minder mooi).
  2. Gebruik een runtime resolver zoals tsconfig-paths (bij ts-node) of bundling.
  3. In ESM/NodeNext setups: gebruik imports in package.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:

  1. node_modules niet geïnstalleerd in image
  2. node_modules overschreven door volume mounts
  3. npm ci --omit=dev terwijl je devDependencies runtime nodig hebt
  4. 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:

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

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

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

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

  1. Lees de stacktrace: welke import faalt, vanuit welk bestand?
  2. Bepaal type import: package vs lokaal pad.
  3. Check installatie: npm ls <pkg> en of node_modules bestaat.
  4. Check environment: Node versie, Docker volumes, CI flags (--omit=dev).
  5. Check module-systeem: ESM vs CJS, extensies, type.
  6. Check build artifacts: bestaat dist/ en klopt main/exports?
  7. 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).