Debugging Node.js
Debugging is an essential skill for any developer. Node.js provides powerful tools for finding and fixing issues in your code. This lesson covers console methods, the built-in debugger, and techniques for effective troubleshooting.
Console Methods
The console object offers more than just console.log():
Basic Logging
console.log('Regular output');
console.info('Informational message');
console.warn('Warning message');
console.error('Error message');
console.debug('Debug message');
Formatting Output
// String substitution
console.log('User %s has %d orders', 'Alice', 5);
// Output: User Alice has 5 orders
// Object inspection
console.log('User:', { name: 'Alice', age: 30 });
// With label
console.log('Config:', JSON.stringify(config, null, 2));
console.table()
Display tabular data clearly:
const users = [
{ name: 'Alice', age: 30, role: 'admin' },
{ name: 'Bob', age: 25, role: 'user' },
{ name: 'Charlie', age: 35, role: 'user' }
];
console.table(users);
Output:
┌─────────┬───────────┬─────┬─────────┐
│ (index) │ name │ age │ role │
├─────────┼───────────┼─────┼─────────┤
│ 0 │ 'Alice' │ 30 │ 'admin' │
│ 1 │ 'Bob' │ 25 │ 'user' │
│ 2 │ 'Charlie' │ 35 │ 'user' │
└─────────┴───────────┴─────┴─────────┘
console.time() and console.timeEnd()
Measure execution time:
console.time('array-operations');
const arr = [];
for (let i = 0; i < 100000; i++) {
arr.push(i * 2);
}
console.timeEnd('array-operations');
// Output: array-operations: 15.234ms
console.count() and console.countReset()
Count how many times code is executed:
function processItem(item) {
console.count('processItem called');
// Process item...
}
processItem('a'); // processItem called: 1
processItem('b'); // processItem called: 2
processItem('c'); // processItem called: 3
console.countReset('processItem called');
processItem('d'); // processItem called: 1
console.trace()
Print a stack trace:
function first() {
second();
}
function second() {
third();
}
function third() {
console.trace('Trace from third');
}
first();
// Outputs:
// Trace: Trace from third
// at third (file.js:10:11)
// at second (file.js:6:3)
// at first (file.js:2:3)
console.group() and console.groupEnd()
Group related log messages:
console.group('User Processing');
console.log('Fetching user...');
console.log('User found: Alice');
console.group('Orders');
console.log('Order 1: $50');
console.log('Order 2: $30');
console.groupEnd();
console.log('Processing complete');
console.groupEnd();
console.assert()
Log only if condition is false:
const user = { name: 'Alice', age: 17 };
console.assert(user.age >= 18, 'User must be 18 or older');
// Outputs: Assertion failed: User must be 18 or older
console.assert(user.name, 'User must have a name');
// No output (condition is true)
The debugger Statement
Insert breakpoints directly in code:
function calculateTotal(items) {
let total = 0;
for (const item of items) {
debugger; // Execution pauses here
total += item.price * item.quantity;
}
return total;
}
Run with debugging:
node inspect app.js
# or
node --inspect-brk app.js
Node.js Inspector
Starting the Inspector
# Start with inspector
node --inspect app.js
# Start with inspector and break on first line
node --inspect-brk app.js
Connecting Chrome DevTools
- Open Chrome and go to
chrome://inspect - Click "Open dedicated DevTools for Node"
- Set breakpoints and debug visually
Debugging Techniques
1. Strategic Logging
function processOrder(order) {
console.log('[processOrder] Input:', order);
const validated = validateOrder(order);
console.log('[processOrder] After validation:', validated);
const total = calculateTotal(validated);
console.log('[processOrder] Total:', total);
return total;
}
2. Debug Variables
const DEBUG = process.env.DEBUG === 'true';
function log(...args) {
if (DEBUG) {
console.log('[DEBUG]', ...args);
}
}
log('This only shows when DEBUG=true');
Debugging Async Code
Tracing Async Operations
async function fetchUserData(userId) {
console.log(`[fetchUserData] Starting for user ${userId}`);
console.time(`fetchUserData-${userId}`);
try {
const user = await getUser(userId);
console.log(`[fetchUserData] User fetched:`, user.name);
const orders = await getOrders(userId);
console.log(`[fetchUserData] Orders fetched:`, orders.length);
return { user, orders };
} catch (error) {
console.error(`[fetchUserData] Error:`, error.message);
throw error;
} finally {
console.timeEnd(`fetchUserData-${userId}`);
}
}
Using async_hooks (Advanced)
const async_hooks = require('async_hooks');
const hook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
console.log(`Async ${type} created: ${asyncId} (from ${triggerAsyncId})`);
}
});
hook.enable();
Common Debugging Patterns
1. Divide and Conquer
// Narrow down the problem
function complexOperation(data) {
console.log('Step 1 input:', data);
const step1Result = step1(data);
console.log('Step 1 output:', step1Result);
console.log('Step 2 input:', step1Result);
const step2Result = step2(step1Result);
console.log('Step 2 output:', step2Result);
// Continue until you find where it breaks
}
2. Rubber Duck Debugging
Explain your code step by step. Often, just explaining the problem helps you see the solution.
3. Binary Search
When a test fails, narrow down by testing half the code at a time.
Debugging Memory Issues
Track Memory Usage
function logMemory(label) {
const used = process.memoryUsage();
console.log(`[${label}] Memory:`, {
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`
});
}
logMemory('Start');
const bigArray = new Array(1000000).fill('data');
logMemory('After array creation');
Find Memory Leaks
// Use --inspect flag and Chrome DevTools Memory tab
// Or use the heapdump module
const heapdump = require('heapdump');
heapdump.writeSnapshot('./heap-' + Date.now() + '.heapsnapshot');
Debugging Tips
- Read error messages carefully - They often tell you exactly what's wrong
- Check the stack trace - Follow it back to your code
- Reproduce the issue - Create a minimal test case
- Use version control - Revert to find when the bug was introduced
- Take breaks - Fresh eyes often spot issues faster
- Log inputs and outputs - At function boundaries
- Check assumptions - Verify what you think is true
Key Takeaways
- Use appropriate console methods (log, warn, error, table, time)
- console.group() organizes related logs
- console.time()/timeEnd() measures performance
- The debugger statement creates breakpoints
- Node.js Inspector connects to Chrome DevTools
- Strategic logging helps trace execution flow
- Use conditional debugging for development vs production
- Track memory usage to find leaks
Summary
Debugging is a critical skill that improves with practice. You've learned various console methods for effective logging, how to use the Node.js Inspector for visual debugging, and techniques for troubleshooting async code and memory issues. These tools will help you find and fix problems quickly.
Next, you'll learn Node.js best practices for writing clean, maintainable, and secure code.

