Console-log debugging works. Until it doesn’t. When we’re chasing a bug in async code with five awaits and a Promise.all, dropping breakpoints is way faster.
Node has a real debugger built in — same protocol Chrome DevTools uses. We just need to start Node with the right flag.
The two flags
--inspect— opens the debug port (default127.0.0.1:9229). Code runs immediately.--inspect-brk— same, but pauses on the very first line, waiting for a debugger to attach.
# Run normally with debugger available
node --inspect server.js
# Pause until DevTools attaches (good for debugging startup code)
node --inspect-brk server.js
In simple language: --inspect is “start running, I’ll attach whenever.” --inspect-brk is “wait for me before doing anything.”
We’ll see this in the terminal:
Debugger listening on ws://127.0.0.1:9229/abc-123
For help, see: https://nodejs.org/en/docs/inspector
Attach with Chrome DevTools
Open Chrome and go to chrome://inspect. Click Configure and make sure localhost:9229 is in the list. Our Node process shows up under “Remote Target” — click inspect.
You get full DevTools: Sources tab for breakpoints, Console for evaluating expressions in the current scope, Memory tab for heap snapshots, Performance tab for CPU profiles.
Attach with VS Code
This is the smoother workflow most of the time. Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug server",
"program": "${workspaceFolder}/server.js",
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "attach",
"name": "Attach to running",
"port": 9229
}
]
}
Two modes:
- Launch — VS Code starts Node with
--inspect-brkitself. Hit F5, done. - Attach — we start Node with
--inspectourselves (e.g. inside Docker), then VS Code connects to port 9229.
Set breakpoints by clicking in the gutter. Hit them by triggering the code path (curl a route, run a script). Use the debug console to evaluate req.body or whatever in the paused scope.
Attach anytime.
Debug a live server.
Waits for attach.
Debug startup/init code.
Debugging inside Docker
The inspect port binds to 127.0.0.1 by default — won’t be reachable from outside the container. Bind to 0.0.0.0 and expose the port:
node --inspect=0.0.0.0:9229 server.js
# docker-compose.yml
services:
app:
ports:
- "9229:9229"
Now VS Code attach config with "port": 9229 works against the container.
Warning: never expose 9229 in production. Anyone who can reach that port has remote code execution on our server.
Useful tricks
- Conditional breakpoints — right-click a breakpoint, set a condition like
userId === 42. Stops only when it matters. - Logpoints — instead of pausing, log a message. Same effect as
console.logbut without editing code. debuggerstatement — drop the keyworddebugger;in our code. If a debugger is attached, execution pauses there. If not, no-op.--inspectwithnodemon—nodemon --inspect server.jsgives auto-restart + debugger together.
When console.log is still fine
Honestly, for a quick “is this code path even running” question, console.log is faster. The breakpoint workflow shines for:
- Inspecting complex object state at a point in time
- Stepping through async/await flow
- Catching an exception at the throw site (enable “Pause on caught exceptions”)
- Debugging a heisenbug we can’t reliably reproduce