エラーの内容
Error: Cannot find module 'express'
Require stack:
- /home/user/myapp/app.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
Node.jsはrequire()やimportが指定されたモジュールを見つけられないときにこのエラーを投げます。主な原因は3つ:パッケージがインストールされていない、ローカルファイルのパスが間違っている、またはnode_modulesが何らかの原因で壊れている、のいずれかです。
根本原因
- パッケージ未インストール —
npm installを一度も実行していないか、別のディレクトリで実行した require('./someFile')の相対パスが間違っている — フォルダ階層がひとつずれているnode_modulesが破損または不完全(インストール途中、ディスク容量不足など)package.jsonにはパッケージが記載されているが、ローカルには実際にインストールされていない- Node.jsのバージョン不一致 — パッケージはNode 18以上を要求しているが、16を使っている
- グローバルインストールのパッケージをローカルプロジェクトで参照している(またはその逆)
修正1: 不足しているパッケージをインストールする
ほとんどのケースはこれで解決します。パッケージが単純に存在しないだけです:
# npm
npm install express
# yarn
yarn add express
# pnpm
pnpm add express
JestやTypeScriptのような開発専用ツールをインストールする場合は、--save-devを使うことでプロダクションビルドが肥大化するのを防げます:
npm install --save-dev jest
簡単な動作確認:
node -e "require('express'); console.log('OK')"
修正2: node_modulesを完全削除して再インストールする
node_modulesがpackage-lock.jsonと同期していないことがあります — インストールの途中、ブランチの切り替え、失敗したアップグレードなどが原因です。クリーンな解決策はこちら:
rm -rf node_modules package-lock.json
npm install
Windows(PowerShell)の場合:
Remove-Item -Recurse -Force node_modules
Remove-Item package-lock.json
npm install
これですべてを削除してゼロから再構築します。ほとんどのプロジェクトでは、新規のnpm installは30〜90秒ほどかかります。
修正3: 正しいディレクトリにいるか確認する
よくある落とし穴です。親フォルダからアプリを実行すると、node_modulesはプロジェクトのサブフォルダに存在するため、現在地からは見つかりません:
# 間違い — node_modulesはここにない
cd /home/user
node myapp/app.js
# 正しい
cd /home/user/myapp
node app.js
node_modulesとpackage.jsonが同じディレクトリにあることを確認:
ls node_modules | grep express
修正4: ローカルファイルパスの間違いを修正する
エラーにCannot find module './utils'と表示されている場合、Node.jsは現在のモジュールからの相対パスでファイルを探していますが、期待した場所にありません。
// 間違い: このパスにファイルが存在しない
const utils = require('./utils');
// ひとつ上の階層を試す
const utils = require('../utils');
// またはサブフォルダの中
const utils = require('./helpers/utils');
Node.jsが認識しているカレントディレクトリがわからない場合は、出力して確認:
console.log(__dirname); // 現在のファイルの絶対パス
修正5: TypeScript / ESMのパス問題
TypeScriptで"moduleResolution": "NodeNext"を使う場合、ソースファイルが.tsであっても.js拡張子でインポートするという独特の挙動があります。見た目は間違っていますが正しい書き方で、コンパイラがビルド時に解決します。
// foo.ts をインポートする — .js拡張子は意図的なもの
import { foo } from './foo.js';
ts-nodeを実行してモジュールエラーが出る場合は、ESMフラグを試してください:
npx ts-node --esm app.ts
またはtsconfig.jsonでより柔軟なリゾルバに切り替える:
{
"compilerOptions": {
"moduleResolution": "node"
}
}
修正6: グローバルとローカルのパッケージ競合
nodemonやts-nodeのようなツールはグローバルにインストールされることが多いですが、package.jsonのスクリプトはローカルのコピーを期待しています。マシン間で一貫性を保つためにdev依存関係としてインストールしましょう:
npm install --save-dev nodemon ts-node
パッケージのインストール場所を確認するには:
npm list express # ローカルインストール
npm list -g express # グローバルインストール
修正7: モノレポ向けのNODE_PATH設定
カスタム構成やモノレポでは、モジュール解決の明示的なヒントが必要な場合があります。ワークスペースのフォルダをまたいで共有パッケージが見つからない場合:
# 1回だけ実行する場合
NODE_PATH=./src node app.js
# package.jsonのスクリプトに組み込む
"scripts": {
"start": "NODE_PATH=./src node app.js"
}
確認手順
- パッケージが記載されているか確認:
cat package.json | grep express - 実際にインストールされているか確認:
ls node_modules/express - requireを直接テスト:
node -e "require('express'); console.log('loaded')" - アプリを実行:
node app.js— エラーが消えているはずです
予防策
package.jsonとpackage-lock.jsonはコミットする —node_modulesは絶対にコミットしない- リポジトリのクローン後や新しい依存関係を含む変更をpullした後は、すぐに
npm installを実行する node_modules/を.gitignoreに追加する — これがないとリポジトリが数百MBも膨れ上がる- CI/CDパイプラインでは
npm ciを使う。npm installと違い、ロックファイルを厳密に守る package.jsonにNode.jsの最低バージョンを指定してエンジンの不一致を早期に検出する:"engines": { "node": ">=18" }

