Debugging Like a Pro
Effective debugging separates senior developers from juniors. Node.js provides powerful built-in debugging tools, and knowing how to use Chrome DevTools, VS Code debugger, and profiling tools will save you hours of console.log-driven guesswork.
🔍 Debugging Toolkit
Built-in Inspector
node --inspect for Chrome DevTools integration
VS Code Debugger
Breakpoints, watch variables, call stack
CPU Profiler
Find performance bottlenecks in your code
Heap Snapshots
Detect and diagnose memory leaks
Console Methods Beyond console.log
// Formatted table output
const users = [
{ name: 'Alice', age: 30, role: 'admin' },
{ name: 'Bob', age: 25, role: 'user' },
];
console.table(users);
// Outputs a formatted ASCII table
// Timing operations
console.time('db-query');
await database.query('SELECT * FROM users');
console.timeEnd('db-query');
// db-query: 45.2ms
// Grouped output
console.group('User Authentication');
console.log('Checking credentials...');
console.log('Validating token...');
console.groupEnd();
// Conditional logging
console.assert(user.age >= 18, 'User must be 18+', user);
// Stack trace without error
console.trace('How did we get here?');
// Count calls
function processItem(item) {
console.count('processItem called');
// processItem called: 1, 2, 3, ...
}
// Styled output (works in browser console)
console.log('%c Error! ', 'background: red; color: white; padding: 2px 8px;', message);
// Object inspection depth
const util = require('util');
console.log(util.inspect(deepObject, { depth: null, colors: true }));
Chrome DevTools for Node.js
// Start Node.js with inspector
// node --inspect server.js # Listen on 127.0.0.1:9229
// node --inspect-brk server.js # Break on first line
// node --inspect=0.0.0.0:9229 app.js # Allow remote connections
// Open Chrome and navigate to: chrome://inspect
// Click "Open dedicated DevTools for Node"
// Programmatic breakpoints
function processOrder(order) {
debugger; // Execution pauses here in DevTools
const total = calculateTotal(order);
return total;
}
// Conditional breakpoints (set in DevTools UI)
// Right-click on line number -> "Add conditional breakpoint"
// Expression: order.total > 1000
// Logpoints (non-breaking breakpoints)
// Right-click -> "Add logpoint"
// Expression: 'Processing order:', order.id
VS Code Debugging
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Server",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.ts",
"runtimeExecutable": "tsx",
"console": "integratedTerminal",
"env": {
"NODE_ENV": "development",
"DATABASE_URL": "postgresql://localhost/mydb"
}
},
{
"name": "Debug Current Test",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/vitest",
"args": ["run", "${relativeFile}"],
"console": "integratedTerminal"
},
{
"name": "Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true
}
]
}
// Tips:
// - F5: Start debugging
// - F9: Toggle breakpoint
// - F10: Step over
// - F11: Step into
// - Shift+F11: Step out
// - Use Watch panel for expressions
// - Use Debug Console for REPL during pause
Memory Leak Detection
// Monitor memory usage
function logMemory() {
const usage = process.memoryUsage();
console.log({
rss: `${(usage.rss / 1024 / 1024).toFixed(2)} MB`, // Total allocated
heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
external: `${(usage.external / 1024 / 1024).toFixed(2)} MB`,
});
}
setInterval(logMemory, 5000);
// Common memory leak patterns:
// 1. Growing arrays/maps that are never cleaned
const cache = new Map();
// FIX: Use LRU cache with max size
// const LRU = require('lru-cache');
// const cache = new LRU({ max: 500 });
// 2. Event listeners not removed
emitter.on('data', handler);
// FIX: Remove listeners when done
emitter.removeListener('data', handler);
// 3. Closures holding references
function createLeak() {
const largeData = new Array(1000000).fill('x');
return function() {
// largeData is captured and never freed
return largeData.length;
};
}
// Generate a heap snapshot for analysis
const v8 = require('v8');
const fs = require('fs');
function takeHeapSnapshot() {
const snapshotStream = v8.writeHeapSnapshot();
console.log('Heap snapshot written to:', snapshotStream);
// Load the .heapsnapshot file in Chrome DevTools Memory tab
}
// Trigger with a signal
process.on('SIGUSR1', takeHeapSnapshot);
CPU Profiling
// Built-in profiler
// node --prof server.js
// (run your workload)
// node --prof-process isolate-*.log > profile.txt
// Programmatic CPU profiling
const inspector = require('inspector');
const fs = require('fs');
const session = new inspector.Session();
session.connect();
function startProfiling() {
session.post('Profiler.enable');
session.post('Profiler.start');
console.log('CPU profiling started');
}
function stopProfiling() {
session.post('Profiler.stop', (err, { profile }) => {
fs.writeFileSync('cpu-profile.cpuprofile', JSON.stringify(profile));
console.log('CPU profile written to cpu-profile.cpuprofile');
// Open in Chrome DevTools -> Performance tab
});
}
// Profile for 10 seconds
startProfiling();
setTimeout(stopProfiling, 10000);
// The debug module for conditional logging
// npm install debug
const debug = require('debug');
const dbLog = debug('app:db');
const httpLog = debug('app:http');
const authLog = debug('app:auth');
dbLog('Connected to database');
httpLog('GET /api/users 200');
authLog('Token validated for user %s', userId);
// Enable with: DEBUG=app:* node server.js
// Or specific: DEBUG=app:db,app:auth node server.js
💡 Key Takeaways
- • Use
console.table,console.time, andconsole.groupfor better logging - •
node --inspectconnects to Chrome DevTools for visual debugging - • Set up VS Code launch.json for one-click debugging
- • Monitor
process.memoryUsage()to detect memory leaks early - • Use the
debugmodule instead of console.log in libraries