エラーの内容
Error: Route.get() requires a callback function but got a [object Undefined]
Expressサーバーが起動時にクラッシュします — リクエストを1件も処理する前に。スタックトレースは問題のある router.get()、app.get()、または router.post() の呼び出し箇所を直接指し示しています。
発生原因
Expressはルートに渡されたすべての引数を検証します。関数以外のもの — undefined を含む — を渡すと即座にエラーをスローします。リカバリも、フォールバックもありません。
根本原因はほぼ必ず次の2つのどちらかです:
- importまたはrequireの問題により
undefinedに解決された変数 - インポート元のパスに存在しない関数(名前付きエクスポートの不一致)
よくある原因と修正方法
1. 名前付きエクスポートの不一致(最も多いケース)
ある名前で関数をエクスポートし、別の名前でインポートしてしまうパターンです。結果はサイレントな undefined となり — 警告なしに起動時クラッシュが発生します。
// controllers/userController.js
const getUsers = (req, res) => res.json([]);
module.exports = { getUsers };
// routes/users.js — 誤り
const { getAllUsers } = require('../controllers/userController'); // undefined!
router.get('/', getAllUsers); // 💥 クラッシュ
// routes/users.js — 正しい
const { getUsers } = require('../controllers/userController');
router.get('/', getUsers);
ルートを登録する前に、実際にインポートされた内容をログ出力して確認しましょう:
const { getUsers } = require('../controllers/userController');
console.log(typeof getUsers); // 'function' = 正常、'undefined' = 問題あり
router.get('/', getUsers);
2. ファイル間の循環依存
循環依存は気づきにくいものです:ファイルAがファイルBをrequireし、ファイルBがファイルAをrequireし返す状況です。Node.jsはこのサイクルを、最初のファイルに対して不完全なモジュールオブジェクトを返すことで解消します — その結果、インポートが undefined になります。
// app.js は routes/users.js を require する
// routes/users.js は app.js を require する ← 循環!
// 修正方法:共有ロジックをニュートラルなファイル(例:db.js や services/user.js)に切り出す
// 両方のファイルはそこからインポートする — 循環なし。
3. エクスポートからミドルウェアを分割代入していない
ミドルウェアファイルが名前付きエクスポートを使用している場合、インポート時に分割代入が必要です。それを省略するとモジュールオブジェクト全体が取得されます — 関数そのものではなく。
// middleware/auth.js
module.exports = { authMiddleware };
// 誤り — モジュールオブジェクトを取得してしまい、関数ではない
const authMiddleware = require('./middleware/auth');
router.get('/profile', authMiddleware, getProfile); // 💥 関数ではなくオブジェクト
// 正しい
const { authMiddleware } = require('./middleware/auth');
4. デフォルトエクスポートと名前付きエクスポートの混同(ESモジュール)
// controllers/postController.js
export const getPosts = (req, res) => res.json([]);
// デフォルトエクスポートなし!
// routes/posts.js — 誤り
import getPosts from '../controllers/postController'; // undefined(デフォルトなし)
router.get('/', getPosts); // 💥
// 正しい
import { getPosts } from '../controllers/postController';
5. ミドルウェアファクトリにreturn文がない
ミドルウェアを生成するファクトリを書いて return を忘れると、Expressは関数の代わりに undefined を受け取ります。
// 誤り — undefinedを返す
function rateLimiter(limit) {
const fn = (req, res, next) => { next(); };
// fnをreturnし忘れている!
}
router.get('/', rateLimiter(10), getUsers); // 💥
// 正しい
function rateLimiter(limit) {
return (req, res, next) => { next(); };
}
router.get('/', rateLimiter(10), getUsers);
6. 非同期の初期化完了前にルートファイルが読み込まれる
コントローラーが非同期で読み込まれるDB接続やコンフィグに依存している場合、早すぎるタイミングでrequireすると、空または部分的に初期化されたモジュールが返されます。
// ルート登録前に初期化を完了させる
await connectDB(); // まずこれを待つ
const userRoutes = require('./routes/users'); // それからルートを読み込む
app.use('/users', userRoutes);
ステップバイステップのデバッグ方法
- スタックトレースを読む。どの
router.get()またはapp.post()呼び出しが失敗したかを正確に示しています。 - その行に移動し、渡す前にすべての引数をログ出力する:
console.log({ myHandler, myMiddleware }); // undefinedを特定する router.get('/path', myMiddleware, myHandler);
- `undefined` になっているものを見つけ、インポートまたは定義された箇所まで遡る。
- エクスポート名、ファイルパス、または不足している `return` 文を修正する。
## 修正の確認
サーバーを再起動して出力を確認します:
node app.js
表示されるべき内容: Server running on port 3000
表示されないはず: Error: Route.get() requires a callback function
該当ルートに直接アクセスして確認します:
curl http://localhost:3000/users
期待する結果:正常なレスポンス — クラッシュではなく
## 再発防止のヒント
- **TypeScriptを使用する** — サーバーが起動する前のコンパイル時にエクスポート/インポートの不一致を検出します。
- **エクスポートスタイルを統一する** — 名前付きエクスポート(`module.exports = { fn }`)かデフォルトエクスポートのどちらかを選び、プロジェクト全体で一貫したスタイルを維持する。両方を混在させるとこのバグの温床になります。
- **CIで循環インポートを禁止する** — `eslint-plugin-import` の `import/no-cycle` ルールで自動的に検出できます。
- **ミドルウェアファクトリには必ずreturnを書く** — 引数を受け取ってミドルウェアを生成する関数は、必ず `return` 文で終わらなければなりません。

