Debugging Node.js Like a Pro
Debugging is an inevitable part of development. But with the right tools and techniques, you can turn it from a chore into a superpower. Here are my favorite ways to debug Node.js like a pro.
1. Use the Built-in Debugger
Node.js has a built-in debugger that you can start with the inspect flag:
node --inspect app.js
Then open chrome://inspect in Chrome. You'll see your app under "Remote Target". Click "inspect" to open DevTools. You get breakpoints, step-through, console, and even memory profiling.
For a simpler CLI experience, use node inspect:
node inspect app.js
This gives you a debug> prompt where you can type commands like cont, next, step, and watch('variable').
2. Leverage the Async Debugging
Node.js 8+ added async hooks to the debugger. When you set a breakpoint inside an async function, the debugger pauses at the correct location, even if the call stack is complex. No more lost async chains!
3. Use debug Module for Logging
Instead of console.log everywhere, use the debug module:
npm install debug
const debug = require('debug')('app:server');
debug('Server started on port %d', port);
Run with:
DEBUG=app:* node app.js
You get color-coded, namespaced logs that you can turn on/off per module. No more messy console.log cleanup.
4. Master the Error Stack Trace
Always create meaningful error messages. Use Error.captureStackTrace to customize stack traces:
class MyError extends Error {
constructor(message) {
super(message);
Error.captureStackTrace(this, this.constructor);
}
}
And when logging errors, log the whole object, not just error.message:
console.error(error); // Good
console.error(error.stack); // Even better
5. Use --trace-warnings and --trace-deprecation
These flags show you where deprecations and warnings originate:
node --trace-warnings app.js
node --trace-deprecation app.js
They print the full stack trace for each warning, so you can fix the root cause immediately.
6. Profile Memory and CPU
The Chrome DevTools profiler is your friend. Use the --inspect flag and go to the "Memory" and "Performance" tabs. Take heap snapshots, record allocation timelines, and find memory leaks.
For CPU profiling, you can also use the built-in --prof flag:
node --prof app.js
This generates an isolate-*.log file. Process it with:
node --prof-process isolate-*.log > profile.txt
You'll get a breakdown of where Node.js spent its time.
7. Use util.inspect for Deep Objects
When you need to log a complex object, console.log often truncates. Use util.inspect with options:
const util = require('util');
console.log(util.inspect(myObject, { showHidden: true, depth: null, colors: true }));
This shows everything, no hidden properties or nested objects cut off.
8. Set Breakpoints in Code
You can add debugger; statements directly in your code. When you run with --inspect, execution will pause there. It's great for conditional debugging:
if (user.role === 'admin') {
debugger; // Only pause for admins
}
9. Use node --check for Syntax Errors
Before running, check for syntax errors:
node --check app.js
It parses the file without executing it. Saves time on trivial mistakes.
10. Automate with nodemon and --inspect
Combine nodemon with the debugger for auto-restart on changes:
nodemon --inspect app.js
Now you can edit and debug without restarting manually.
Final Thoughts
Debugging is a skill. The more tools you have, the faster you find bugs. Start with the built-in debugger, add debug for logging, and profile memory early. Your future self will thank you.
Top comments (0)