Fix TypeError: Cannot read properties of undefined (reading 'propertyName') in Node.js

beginner💚 Node.js2026-03-18| Node.js 14+, any OS (Linux, macOS, Windows)

Error Message

TypeError: Cannot read properties of undefined (reading 'propertyName')
#Node.js#TypeError#undefined

What happened

You're running a Node.js app and suddenly hit this:

TypeError: Cannot read properties of undefined (reading 'propertyName')
    at Object.<anonymous> (/app/index.js:12:20)
    at Module._compile (node:internal/modules/cjs/loader:1364:14)

The variable you're reading from is undefined — not null, not an empty object, but completely absent. Node.js 16+ introduced this cleaner error message; older versions just said "Cannot read property 'x' of undefined".

Common causes

  • Accessing a nested property before the parent object is populated
  • An async function returns undefined because you forgot await
  • A function parameter is not passed at the call site
  • An array element is accessed at an out-of-bounds index
  • A destructured key doesn't exist in the source object
  • A missing return statement in a function that produces the value

Debug process

Step 1 — Read the stack trace

The trace gives you the exact file and line. Go there first. Example:

TypeError: Cannot read properties of undefined (reading 'email')
    at sendWelcome (/app/services/user.js:24:30)

Open user.js line 24 and look at what's on the left side of .email.

Step 2 — Log the value before accessing it

Drop a console.log right above the offending line:

// user.js line 23
console.log('user object:', user);
const email = user.email; // line 24 — crashes here

If the log prints undefined, the variable was never set.

Step 3 — Check for missing await

Missing await is responsible for a large share of these errors in async code. Without it, you get a Promise object — not the resolved value:

// Bug: forgot await
const user = getUser(id);       // returns a Promise, not the user
console.log(user.email);        // TypeError

// Fix
const user = await getUser(id);
console.log(user.email);        // works

Step 4 — Trace where the value comes from

Work backwards. If user is undefined, where does user come from? A database query? An API response? A function argument? Check each link in the chain.

// Database query returning null/undefined
const user = await db.users.findOne({ id });
// If no record found, user is null or undefined
console.log(user.email); // TypeError

Solutions

Option 1 — Guard with optional chaining (Node.js 14+)

The ?. operator short-circuits and returns undefined instead of throwing, making it safe to read nested properties that may not exist:

// Instead of:
const city = user.address.city;

// Use:
const city = user?.address?.city;
console.log(city); // undefined (no crash)

Best for read-only access where a missing value is acceptable behavior.

Option 2 — Explicit existence check

If missing data is an error condition, fail loudly with a proper status code:

const user = await db.users.findOne({ id });

if (!user) {
  return res.status(404).json({ error: 'User not found' });
}

// Safe beyond this point
console.log(user.email);

Option 3 — Default values with nullish coalescing

Combine ?. with ?? to fall back gracefully when a config object is missing entirely:

const config = loadConfig();

// If config is undefined, fall back to defaults
const timeout = config?.timeout ?? 3000;
const retries = config?.retries ?? 3;

Option 4 — Fix missing return in a function

Easy to miss, especially in functions with multiple branches:

// Bug: one branch forgets to return
function getUser(id) {
  if (id > 0) {
    return { id, name: 'Alice' };
  }
  // implicitly returns undefined for id  0) {
    return { id, name: 'Alice' };
  }
  return null; // or throw an error
}

Option 5 — Destructuring with defaults

When a caller omits an argument, destructured properties crash immediately. Give the parameter a safe default:

// Bug
function process({ name, options }) {
  console.log(options.timeout); // TypeError if options not passed
}

// Fix — provide default for the whole param or nested keys
function process({ name, options = {} }) {
  console.log(options.timeout); // undefined, not a crash
}

Option 6 — Array bounds check

Accessing index 0 on an empty array returns undefined. Check length first, or use optional chaining:

const items = [];

// Bug
console.log(items[0].name); // TypeError

// Fix
if (items.length > 0) {
  console.log(items[0].name);
}

// Or with optional chaining
console.log(items[0]?.name);

Verification

Run the app and confirm the error no longer appears:

# Run your app or test suite
node index.js

# Or run tests
npm test

Optional chaining silently swallows missing values. If you used it as a fallback, add an assertion to confirm you're getting real data — not just undefined with no crash:

const email = user?.email;
console.assert(email !== undefined, 'Expected user.email to be set');

Lessons learned

  • Never assume an async function returned successfully — always check the result before accessing properties.
  • Optional chaining is not a cure-all — it hides the error, but the value is still missing. Use it when absence is genuinely expected; use a guard when absence is a bug.
  • TypeScript catches most of this before you run anything — if you're starting a new Node.js service, add TypeScript. It flags user.email when user could be undefined at compile time, not at 2 AM in production.
  • The stack trace is your friend — the file and line are always there. Read it before guessing.

Related Error Notes