何が起きたか
Node.jsアプリを実行中に、突然次のエラーに遭遇しました。
TypeError: Cannot read properties of undefined (reading 'propertyName')
at Object. (/app/index.js:12:20)
at Module._compile (node:internal/modules/cjs/loader:1364:14)
読み取ろうとしている変数はundefinedです。nullでも空のオブジェクトでもなく、完全に存在しません。Node.js 16以降ではこのより明確なエラーメッセージが導入されました。古いバージョンでは単に「Cannot read property 'x' of undefined」と表示されていました。
一般的な原因
- 親オブジェクトが設定される前にネストされたプロパティにアクセスしている
awaitを忘れたため、非同期関数がundefinedを返している- 関数呼び出し時に引数が渡されていない
- 配列の範囲外のインデックスにアクセスしている
- 分割代入されたキーがソースオブジェクトに存在しない
- 値を生成する関数で
return文が抜けている
デバッグプロセス
ステップ1 — スタックトレースを読む
トレースは正確なファイルと行を示します。まずそこを確認してください。例:
TypeError: Cannot read properties of undefined (reading 'email')
at sendWelcome (/app/services/user.js:24:30)
user.jsの24行目を開き、.emailの左側にあるものを見てください。
ステップ2 — アクセスする前に値をログ出力する
問題の行の直前にconsole.logを挿入してください。
// user.js 23行目
console.log('user object:', user);
const email = user.email; // 24行目 — ここでクラッシュする
ログにundefinedと表示された場合、その変数は一度も設定されていません。
ステップ3 — awaitの欠落を確認する
非同期コードにおけるこれらのエラーの大部分は、awaitの欠落が原因です。awaitがないと、解決された値ではなくPromiseオブジェクトが得られます。
// バグ: awaitを忘れている
const user = getUser(id); // ユーザーではなくPromiseを返す
console.log(user.email); // TypeError
// 修正
const user = await getUser(id);
console.log(user.email); // 動作する
ステップ4 — 値の出所を追跡する
さかのぼって確認してください。もしuserがundefinedなら、userはどこから来ているのでしょうか?データベースクエリから?APIレスポンスから?関数の引数から?チェーンの各リンクをチェックしてください。
// null/undefinedを返すデータベースクエリ
const user = await db.users.findOne({ id });
// レコードが見つからない場合、userはnullまたはundefinedになる
console.log(user.email); // TypeError
解決策
選択肢1 — オプショナルチェイニングで保護する (Node.js 14+)
?.演算子は、エラーをスローする代わりにショートサーキットしてundefinedを返すため、存在しない可能性のあるネストされたプロパティを安全に読み取ることができます。
// 以下の代わりに:
const city = user.address.city;
// 以下を使用する:
const city = user?.address?.city;
console.log(city); // undefined (クラッシュしない)
値の欠落が許容される読み取り専用アクセスに最適です。
選択肢2 — 明示的な存在チェック
データ欠落がエラー条件である場合、適切なステータスコードで明確に失敗させてください。
const user = await db.users.findOne({ id });
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// この時点以降は安全
console.log(user.email);
選択肢3 — null合体演算子によるデフォルト値
?.と??を組み合わせて、設定オブジェクトが完全に欠落している場合に適切にフォールバックさせます。
const config = loadConfig();
// configがundefinedの場合、デフォルト値にフォールバックする
const timeout = config?.timeout ?? 3000;
const retries = config?.retries ?? 3;
選択肢4 — 関数内のreturnの欠落を修正する
特に複数の分岐がある関数では見落としがちです。
// バグ: ある分岐でreturnが抜けている
function getUser(id) {
if (id > 0) {
return { id, name: 'Alice' };
}
// id 0) {
return { id, name: 'Alice' };
}
return null; // またはエラーをスローする
}
選択肢5 — デフォルト値を使った分割代入
呼び出し元が引数を省略した場合、分割代入されたプロパティは即座にクラッシュします。パラメータに安全なデフォルト値を与えてください。
// バグ
function process({ name, options }) {
console.log(options.timeout); // optionsが渡されない場合TypeError
}
// 修正 — パラメータ全体またはネストされたキーにデフォルト値を提供する
function process({ name, options = {} }) {
console.log(options.timeout); // undefinedになり、クラッシュしない
}
選択肢6 — 配列の範囲チェック
空の配列のインデックス0にアクセスするとundefinedが返されます。まず長さを確認するか、オプショナルチェイニングを使用してください。
const items = [];
// バグ
console.log(items[0].name); // TypeError
// 修正
if (items.length > 0) {
console.log(items[0].name);
}
// またはオプショナルチェイニングを使用する
console.log(items[0]?.name);
検証
アプリを実行し、エラーが表示されなくなったことを確認してください。
# アプリまたはテストスイートを実行する
node index.js
# またはテストを実行する
npm test
オプショナルチェイニングは、値の欠落を静かに処理します。フォールバックとして使用した場合、クラッシュせずに単にundefinedが返されるのではなく、実際のデータが取得されていることを確認するためにアサーションを追加してください。
const email = user?.email;
console.assert(email !== undefined, 'Expected user.email to be set');
学んだこと
- 非同期関数が正常に返されたと決して仮定しないこと — プロパティにアクセスする前に必ず結果を確認してください。
- オプショナルチェイニングは万能ではない — エラーを隠しますが、値は依然として欠落しています。欠落が本当に予期される場合にのみ使用し、欠落がバグである場合はガードを使用してください。
- TypeScriptは実行前にほとんどのこれを捕捉する — 新しいNode.jsサービスを開始するなら、TypeScriptを導入してください。プロダクションで午前2時にではなく、コンパイル時に
userがundefinedになりうる場合にuser.emailを指摘してくれます。 - スタックトレースはあなたの味方である — ファイルと行は常にそこにあります。推測する前に必ず読んでください。

