Node.js Debugger and Inspector: Debug JavaScript with Chrome DevTools

Node.js debugging lets you pause running JavaScript, inspect variables, step through code, and find the exact line where a bug happens. The inspector is the modern debugging interface that connects Node.js to tools like Chrome DevTools and other compatible debuggers.

Quick answer: Start Node.js with --inspect or --inspect-brk, then connect a debugger to the reported port. Use --inspect-brk when you want execution to pause before your code runs.

Difficulty: Beginner

You'll understand this better if you know: basic JavaScript syntax, how to run Node.js programs from the terminal, and what variables, functions, and errors are.

1. What Is the Node.js Debugger and Inspector?

The Node.js debugger and inspector are tools for examining a running Node.js process. They let you pause execution, move through code one statement at a time, and inspect values without adding extra console.log() statements everywhere.

Older Node.js workflows used a command-line debugger, but the inspector is now the standard way to debug most Node.js applications.

2. Why the Node.js Inspector Matters

Debugging is easier when you can see the program in motion instead of guessing from logs. The inspector helps you understand state at the moment a problem occurs, which is especially useful for asynchronous code, event handlers, and data transformations.

It matters because many Node.js bugs are timing-related or depend on hidden state. A debugger lets you answer questions like:

Use the inspector when logs are not enough, when you need to compare expected and actual values, or when a bug only appears under specific conditions.

3. Basic Syntax or Core Idea

Starting Node.js with the inspector

The simplest way to enable debugging is to launch your script with an inspector flag. This opens a debug port and waits for a debugger to connect.

node --inspect app.js

This starts the program normally and exposes the inspector on a local port. If you want Node.js to stop immediately before running your code, use --inspect-brk instead.

node --inspect-brk app.js

How the connection works

When the process starts, Node.js prints a WebSocket debug address such as a localhost port. A compatible debugger attaches to that port and sends commands like “pause,” “resume,” and “step over.”

Minimal example script

Here is a tiny program that we can inspect in the debugger.

function greet(name) {
  const message = `Hello, ${name}`;
  return message;
}

const result = greet("Ada");
console.log(result);

With a breakpoint inside greet(), you can inspect name, message, and the current call stack before the function returns.

4. Step-by-Step Examples

Example 1: Debugging a simple function

Start by pausing on the first line, then step into the function call. This is the best way to learn how the debugger follows execution.

node --inspect-brk app.js

Once the debugger is attached, set a breakpoint on the return line and inspect the values in scope. You can confirm whether the function receives the argument you expected.

Example 2: Finding a wrong calculation

Suppose a total is off by a small amount. A breakpoint lets you check each intermediate value instead of only seeing the final wrong result.

function calculateTotal(price, taxRate) {
  const tax = price * taxRate;
  const total = price + tax;
  return total;
}

Stop at the line that computes tax and inspect both inputs. If taxRate is already incorrect, the bug is earlier in the call path.

Example 3: Inspecting asynchronous code

Async bugs often happen between callbacks, promises, or timers. The inspector helps you pause at the point where a value becomes unexpected.

async function loadUser(id) {
  const response = await fetchUser(id);
  return response.json();
}

Step through the await boundary and inspect the resolved value. This is useful when an API response shape differs from what your code expects.

Example 4: Debugging an error path

Breakpoints are also useful around error handling. You can pause right before an exception is thrown and inspect the values that trigger it.

function parseAge(value) {
  if (!value) {
    throw new Error("Age is required");
  }

  return Number(value);
}

By pausing before the throw, you can verify whether the function received undefined, an empty string, or an unexpected type.

5. Practical Use Cases

The inspector is most helpful when the problem depends on runtime values rather than syntax alone.

6. Common Mistakes

Mistake 1: Using console.log() instead of pausing the program

Logging is useful, but it can hide timing issues and make it hard to inspect nested objects at the moment of failure. Debugging is easier when you pause execution and inspect values directly.

Problem: The program keeps running, so you only see printed snapshots instead of the exact state at the bug location.

console.log("value:", data);
doWork(data);

Fix: Pause on a breakpoint or start with --inspect-brk so you can inspect the live state.

// Set a breakpoint here in your editor or DevTools
const result = doWork(data);

The corrected approach works because you can inspect values before they change.

Mistake 2: Forgetting to pause on the first line

If your code runs too quickly, the debugger may attach after the important code has already executed. That makes it seem like breakpoints are not working.

Problem: Without --inspect-brk, the program may finish or pass the important line before you attach the debugger.

node --inspect app.js

Fix: Use --inspect-brk when you need time to attach before application code runs.

node --inspect-brk app.js

This works because Node.js pauses before the first statement and waits for the debugger.

Mistake 3: Setting breakpoints on code that is not loaded

Breakpoints only work in files that the running process has actually loaded. If a file is never imported or the path is wrong, the breakpoint will remain unbound.

Problem: The debugger cannot stop in code that the current process has not executed or loaded yet.

const helper = require("./helpers/math.js");
// but the file on disk is actually ./helper/math.js

Fix: Verify the import path and confirm the file is part of the running process.

const helper = require("./helper/math.js");

Once the correct file is loaded, the breakpoint becomes active and execution can stop there.

Mistake 4: Assuming async code will pause exactly like sync code

Async functions split execution into separate steps, so a breakpoint before await does not behave the same as one after it. Beginners often think the debugger skipped a line when it actually resumed later.

Problem: The debugger pauses before the promise resolves, then continues in a later turn of the event loop.

async function loadData() {
  const response = await fetch("/api/data");
  return await response.json();
}

Fix: Place breakpoints on both sides of the await and inspect the resolved value carefully.

async function loadData() {
  const response = await fetch("/api/data");
  // Inspect response here before parsing
  return response.json();
}

The corrected version helps you see which step introduces the wrong value.

7. Best Practices

Prefer --inspect-brk for startup bugs

If the bug happens during initialization, attach before the first line runs. This avoids missing one-time setup code and makes startup state easier to inspect.

node --inspect-brk server.js

This is better than attaching later because initialization code often runs only once.

Use meaningful breakpoint locations

Break on lines that represent decisions, transformations, or data boundaries rather than every single statement. That keeps the debugging session focused.

function normalizeEmail(email) {
  const trimmed = email.trim();
  return trimmed.toLowerCase();
}

Setting a breakpoint on the return line is usually more helpful than stopping on every intermediate assignment.

Inspect objects instead of copying values into logs

The debugger can expand objects and show nested fields without formatting them manually. That is especially useful for large API responses and class instances.

const user = {
  id: 42,
  profile: {
    name: "Ada",
    active: true
  }
};

This is more reliable than building long debug strings, and it gives you access to the real runtime object.

8. Limitations and Edge Cases

Warning: Do not expose the Node.js inspector port on an untrusted network. Debug access can reveal process state and may allow code execution in the running process.

9. Practical Mini Project

Let’s debug a small Node.js script that processes a list of orders and computes a total. The goal is to find why one order is producing the wrong result.

function calculateOrderTotal(items) {
  let total = 0;

  for (const item of items) {
    total += item.price * item.quantity;
  }

  return total;
}

const orders = [
  { price: 10, quantity: 2 },
  { price: 5, quantity: 3 }
];

console.log(calculateOrderTotal(orders));

Run it with the inspector, attach your debugger, and pause inside the loop. Inspect item.price, item.quantity, and total after each iteration. If the result is wrong, you can determine whether the bug is in the data or in the calculation logic.

This example shows the main debugging workflow: start with the inspector, pause where the logic matters, inspect live values, and then step forward until the source of the bug is clear.

10. Key Points

11. Practice Exercise

Try debugging this script to find why it prints the wrong average.

Expected output: The program should print 20 for the sample data.

Hint: Watch the division carefully and make sure you are using the right total.

function average(values) {
  let sum = 0;

  for (const value of values) {
    sum += value;
  }

  return sum / values.length;
}

const numbers = [10, 20, 30];
console.log(average(numbers));

12. Final Summary

The Node.js debugger and inspector are essential tools for understanding what your code is doing while it runs. They let you pause execution, inspect variables, follow the call stack, and track down bugs that are difficult to see with logs alone.

In practice, the most useful commands are --inspect and --inspect-brk. Use them to attach a debugger, set breakpoints in meaningful places, and step through both synchronous and asynchronous code with confidence.

Once you are comfortable with the inspector, the next step is to learn how source maps and IDE integrations make debugging transpiled or bundled JavaScript easier.