Back to News & Insights
JavaScript September 17, 2026 · 3 min read

Debugging Node.js Like a Pro

Most Node.js debugging happens with console.log. That works until it doesn't: async stacks that point...

Debugging Node.js Like a Pro

Most Node.js debugging happens with console.log. That works until it doesn't: async stacks that point to the wrong place, a variable that changes between the log and the crash, or a server that only misbehaves on one request out of a thousand. Here's the toolkit I reach for instead.

The built-in inspector gives you breakpoints, step-through, and a real call stack. Start your process with:

Then open chrome://inspect in Chrome and click "inspect". For a process you can't restart easily (a worker, a Docker container), use --inspect-brk to pause on the first line, or send SIGUSR1 to a running process to enable the inspector on the fly.

If the port needs to be reachable from outside a container, bind it explicitly: node --inspect=0.0.0.0:9229 app.js. Don't do this on a public host.

A plain breakpoint inside a hot loop stops on every iteration. Right-click the line in DevTools and add a conditional breakpoint. Instead of:

Set the condition to order.id === 'abc123' and it stops exactly once. This turns a flaky bug into a reproducible one.

A debugger; line behaves like a breakpoint when the inspector is attached, and is a no-op otherwise. That makes it safe to commit temporarily:

console.log('user:', user) prints [object Object] in some terminals and truncates deeply nested data. Two fixes:

console.dir with depth: null is the one I use most. It respects circular references, which JSON.stringify throws on.

Async stack traces are on by default in modern Node, but if you're on an older release or have them disabled, enable them:

Without this, an error thrown inside a setTimeout or a promise chain shows a stack that starts at the callback, not at the code that scheduled it. With it, you see the full causal chain.

This writes a .cpuprofile file you can load in Chrome DevTools under the Performance tab. Look for wide bars: those are the functions eating your time. For memory, --heap-prof does the same for allocations, and process.memoryUsage() gives you a cheap snapshot:

A heap that climbs steadily and never drops is a leak. A heap that sawtooths is just GC doing its job.

EADDRINUSE, ECONNREFUSED, and ENOENT tell you the category of failure instantly. Log the whole error object, not err.message.

A quick checklist Reproduce it reliably first. A conditional breakpoint beats guessing. --inspect-brk for startup crashes, SIGUSR1 for running processes. console.dir(obj, { depth: null }) over string concatenation. --cpu-prof and --heap-prof for performance, not breakpoints. Check err.code before you check err.message.

None of this is exotic. It's all built into Node. The trick is remembering it exists before you add the twentieth console.log.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation