DEV Community

Stack Horizon
Stack Horizon

Posted on

Debugging Node.js Like a Pro

Start with the Built-in Inspector

Before reaching for external tools, remember Node.js has a built-in debugger. Run your script with --inspect and open chrome://inspect in Chrome to get a full DevTools experience: breakpoints, step-through, console, and even memory profiling.

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

For a quick breakpoint without touching the browser, use --inspect-brk to pause on the first line. This is great for debugging startup issues.

Use debugger Statements and Conditional Breakpoints

Sometimes you need a breakpoint only when a condition is true. Instead of littering your code with if blocks, set a conditional breakpoint in DevTools. Right-click the line number, choose "Add conditional breakpoint," and enter an expression like user.id === 42.

For quick inline debugging, debugger; works but remember to remove it before committing. I often use it temporarily when I'm too lazy to open the DevTools UI.

Log Like a Pro with util.inspect

console.log of an object prints [object Object] which is useless. Use util.inspect with depth and colors to see nested structures clearly.

const util = require('util');
console.log(util.inspect(myObject, { showHidden: false, depth: null, colors: true }));
Enter fullscreen mode Exit fullscreen mode

Or in modern Node, you can use console.dir with { depth: null } for the same effect.

Async Stack Traces: Don't Lose the Context

Async errors are painful because stack traces often end at the event loop. Node 12+ gives you better async stack traces by default, but you can improve them further by using Error.captureStackTrace in your own error classes.

class MyError extends Error {
  constructor(message) {
    super(message);
    Error.captureStackTrace(this, MyError);
  }
}
Enter fullscreen mode Exit fullscreen mode

This makes the stack trace point to the caller, not the constructor.

Handle Unhandled Rejections and Exceptions

Silent failures are the worst. Set up global handlers to log errors properly and exit gracefully.

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
  // Decide whether to crash or continue
});

process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  // For production, you might want to clean up and exit
});
Enter fullscreen mode Exit fullscreen mode

Use --trace-warnings and --trace-deprecation

When upgrading dependencies, warnings tell you about deprecated APIs. Run with --trace-warnings to see exactly where the warning originates. --trace-deprecation gives you stack traces for deprecated calls, saving you hours of hunting.

node --trace-deprecation app.js
Enter fullscreen mode Exit fullscreen mode

Debug Memory Leaks with Heap Snapshots

If your app's memory grows over time, take heap snapshots in Chrome DevTools (Memory tab). Compare two snapshots taken at different times to see which objects are accumulating. Look for closures that capture large variables or event listeners that are never removed.

Profile CPU Usage

Use the built-in profiler to find hot spots. Run with --prof and then process the output with node --prof-process.

node --prof app.js
node --prof-process isolate-*.log > profile.txt
Enter fullscreen mode Exit fullscreen mode

The text file shows you which functions consume the most CPU. This is invaluable for optimizing performance.

Use node --watch for Development

Node 18+ includes --watch to restart on file changes. Combine it with --inspect to get auto-restart plus debugging.

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

Debug with Environment Variables

Make your app's behavior configurable via env vars, so you can turn on verbose logging or mock external services without code changes.

const DEBUG = process.env.DEBUG === 'true';
if (DEBUG) console.log('Detailed info');
Enter fullscreen mode Exit fullscreen mode

Use libraries like debug to namespace loggers and filter them via DEBUG=app:*.

Final Thoughts

Mastering these techniques will save you countless hours. Start with the built-in inspector, learn to read stack traces properly, and don't ignore warnings. Debugging is a skill, and the more tools you have, the faster you'll find those pesky bugs.

Top comments (0)