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.
- --inspect starts the inspector so a debugger can attach while the program runs.
- --inspect-brk starts the inspector and pauses on the first line before any application code executes.
- Breakpoints stop execution at chosen lines so you can inspect scope, call stacks, and object values.
- The inspector protocol is what modern tools use to communicate with Node.js.
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:
- What value did this function actually receive?
- Which branch of the code ran?
- Why is an object missing a property here?
- Where did this error originate before it was caught?
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.jsThis 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.jsHow 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.jsOnce 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
- Tracking down a failing route handler in a Node.js HTTP server.
- Inspecting the parsed output of a JSON file or API response.
- Stepping through business logic that transforms data before saving it.
- Debugging asynchronous code that behaves differently in development and production.
- Finding the exact line that produces a runtime exception or rejected promise.
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.jsFix: Use --inspect-brk when you need time to attach before application code runs.
node --inspect-brk app.jsThis 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.jsFix: 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.jsThis 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
- The inspector listens on a local debug port, so you must secure or restrict access when exposing it beyond your machine.
- Code running in child processes or worker threads may need separate inspector attachment or extra configuration.
- Source maps matter when you debug transpiled code, because the runtime file may not match the original source line numbers.
- If your program exits immediately, you may need --inspect-brk to keep it paused long enough to attach.
- Breakpoints can appear unbound when files are loaded lazily, bundled, or referenced through a path that differs from what the editor expects.
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
- The Node.js inspector lets you pause and inspect live code instead of guessing from logs.
- --inspect enables debugging, while --inspect-brk pauses before the first line.
- Breakpoints, stepping, and scope inspection are the core tools you will use most often.
- Async code, startup code, and file-loading issues are common places where debugging becomes especially valuable.
- Source maps, correct paths, and secure port handling matter when debugging real projects.
11. Practice Exercise
Try debugging this script to find why it prints the wrong average.
- Run the script with the inspector.
- Pause inside the calculation function.
- Inspect the array length and sum values.
- Fix the bug so the average is correct.
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.