TechLead
Lesson 12 of 16
5 min read
Node.js

Debugging Node.js Applications

Debug effectively with Chrome DevTools, VS Code debugger, memory leak detection, CPU profiling, heap snapshots, and advanced console methods

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, and console.group for better logging
  • node --inspect connects 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 debug module instead of console.log in libraries

Continue Learning