Debugging with the Node.js Inspector
When console.log stops being enough, Node.js ships a full debugger built right in. The inspector exposes a real debugging protocol over a WebSocket, letting you pause execution, set breakpoints, step through code line by line, and inspect live variable values — all without installing anything. You can drive it from a terminal, from Chrome DevTools, or from your editor. Learning to attach a debugger turns guesswork into precise observation.
Starting the inspector
Every Node.js runtime since v6 includes the V8 Inspector. You activate it with one of two flags. The --inspect flag opens a debugging port and lets the program run normally, so you can attach a client whenever you like. The --inspect-brk flag does the same but pauses on the very first line, which is ideal when you need to set breakpoints before any code executes.
node --inspect app.js
node --inspect-brk app.js
Output:
Debugger listening on ws://127.0.0.1:9229/0f2c4a1b-9e3d-4f8a-bc12-7a6d5e0f1234
For help, see: https://nodejs.org/en/docs/inspector
By default the inspector binds to 127.0.0.1:9229. You can change the host and port — useful inside containers — by passing an address: node --inspect=0.0.0.0:9230 app.js.
Warning: Exposing the inspector on a public interface (
0.0.0.0) grants full code execution to anyone who can reach the port. Only do this on trusted networks, and never in production.
The built-in CLI debugger
You don’t need a browser at all. Running node inspect (note the subcommand, not the flag) launches a built-in command-line debugger that connects to a child process for you and drops you into an interactive prompt.
node inspect app.js
From the debug> prompt you control execution with short commands:
| Command | Action |
|---|---|
cont / c | Continue until the next breakpoint |
next / n | Step over the current line |
step / s | Step into a function call |
out / o | Step out of the current function |
pause | Pause running code |
setBreakpoint(line) / sb(line) | Set a breakpoint |
repl | Open a REPL to evaluate expressions in scope |
watch('expr') | Re-evaluate an expression at every break |
restart | Restart the script |
.exit | Quit the debugger |
Inside repl mode you can read and even mutate local variables, which makes it easy to confirm exactly what state your code sees at the moment it pauses.
The debugger statement
The single most reliable way to stop on a specific line is the debugger statement. When a debugger is attached, execution halts there as if you had set a breakpoint; when no debugger is attached, the statement does nothing, so it is safe to leave in temporarily.
function calculateTotal(items) {
let total = 0;
for (const item of items) {
debugger; // execution pauses here when a debugger is attached
total += item.price * item.qty;
}
return total;
}
const cart = [
{ price: 9.99, qty: 2 },
{ price: 4.5, qty: 3 },
];
console.log(calculateTotal(cart));
Run it with node --inspect-brk cart.js, attach a client, and the program will stop on each loop iteration so you can inspect total, item, and the loop index.
Connecting Chrome DevTools
Chrome offers the richest inspector UI, with a source viewer, scope panels, a console, and the V8 profiler. To connect, open chrome://inspect in Chrome or Edge. Your running Node.js target appears under Remote Target — click inspect to open a dedicated DevTools window wired to your process.
If the target doesn’t appear automatically, click Configure and confirm that localhost:9229 is in the discovery list. Alternatively, copy the devtools:// URL printed in some Node.js versions, or open the dedicated DevTools front end directly.
Once connected you can:
- Click the gutter in the Sources panel to set and remove breakpoints.
- Use the step controls (step over, step into, step out, resume) to walk through code.
- Hover over any variable to see its current value, or read the Scope pane.
- Add expressions to Watch and set conditional or logpoint breakpoints from the gutter context menu.
- Switch to Memory to take heap snapshots or Profiler for CPU profiles.
Tip: Breakpoints set in Chrome DevTools survive restarts of the same script as long as the file path is unchanged, so you can iterate quickly with
--inspect-brkandrestart.
Debugging in VS Code
Most developers attach a debugger from their editor rather than a browser. VS Code has first-class Node.js support and speaks the same inspector protocol. The fastest path is the JavaScript Debug Terminal: open the command palette, run Debug: JavaScript Debug Terminal, then launch your program normally with node app.js — VS Code attaches automatically and honors breakpoints set in the gutter.
For repeatable setups, add a launch.json configuration:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch app",
"program": "${workspaceFolder}/app.js",
"skipFiles": ["<node_internals>/**"]
}
]
}
Use "request": "attach" with a "port": 9229 instead when the process is already running under --inspect. The skipFiles entry keeps the debugger from stepping into Node.js internals.
Best Practices
- Reach for
--inspect-brk(not--inspect) when the bug occurs during startup, so nothing runs before your client attaches. - Prefer conditional breakpoints and logpoints over scattering
console.logcalls — they require no code changes and no restart. - Keep the inspector bound to
127.0.0.1and tunnel over SSH instead of exposing the port on a network. - Leave a
debuggerstatement in only while actively debugging; remove it before committing so it never ships. - Use the editor’s JavaScript Debug Terminal for the lowest-friction setup, and a committed
launch.jsonfor shared, reproducible configs. - Combine breakpoints with the DevTools Memory and Profiler tabs to investigate leaks and hot paths in the same session.