DEV Community

Stack Horizon
Stack Horizon

Posted on

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.

Start with --inspect, not print statements

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

node --inspect app.js
Enter fullscreen mode Exit fullscreen mode

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.

kill -USR1 <pid>
Enter fullscreen mode Exit fullscreen mode

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.

Break on the condition, not the line

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

for (const order of orders) {
  processOrder(order); // stops 10,000 times
}
Enter fullscreen mode Exit fullscreen mode

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

Use debugger statements deliberately

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:

function applyDiscount(cart) {
  debugger; // only pauses when --inspect is active
  return cart.total * 0.9;
}
Enter fullscreen mode Exit fullscreen mode

I'll leave these in during a debugging session and strip them before the PR.

Log objects, not strings

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

console.log(JSON.stringify(user, null, 2));
console.dir(user, { depth: null });
Enter fullscreen mode Exit fullscreen mode

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

Trace async properly with --async-stack-traces

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

node --async-stack-traces app.js
Enter fullscreen mode Exit fullscreen mode

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.

Attach a profiler when it's slow, not broken

If the bug is "it's slow," breakpoints won't help. Use the built-in profiler:

node --cpu-prof --cpu-prof-dir=./profiles app.js
Enter fullscreen mode Exit fullscreen mode

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:

setInterval(() => {
  const { heapUsed } = process.memoryUsage();
  console.log(`heap: ${(heapUsed / 1024 / 1024).toFixed(1)} MB`);
}, 5000);
Enter fullscreen mode Exit fullscreen mode

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

Read the error, including code

Node errors carry structured fields that err.message hides:

server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.error(`Port ${err.port} is taken`);
  }
  console.error(err);
});
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)