Effective debugging saves hours of frustration. Here's how to debug Node.js applications like a pro.
Console Methods#
1// Beyond console.log
2const user = { id: 1, name: 'John', email: 'john@example.com' };
3const users = [user, { id: 2, name: 'Jane' }];
4
5// Formatted output
6console.log('User:', user);
7console.log('User: %o', user); // Object format
8console.log('Name: %s, ID: %d', user.name, user.id);
9
10// Table format for arrays/objects
11console.table(users);
12console.table(users, ['id', 'name']); // Specific columns
13
14// Grouped output
15console.group('User Details');
16console.log('ID:', user.id);
17console.log('Name:', user.name);
18console.groupEnd();
19
20// Collapsed group
21console.groupCollapsed('Debug Info');
22console.log('Detailed info here');
23console.groupEnd();
24
25// Timing
26console.time('fetchData');
27await fetchData();
28console.timeEnd('fetchData'); // fetchData: 150.23ms
29
30// Count calls
31function processItem() {
32 console.count('processItem called');
33}
34
35// Stack trace
36console.trace('Where am I?');
37
38// Assertions
39console.assert(user.id > 0, 'User ID should be positive');
40
41// Clear console
42console.clear();Node.js Inspector#
1# Start with inspector
2node --inspect app.js
3
4# Break on first line
5node --inspect-brk app.js
6
7# Custom port
8node --inspect=9230 app.js
9
10# Connect Chrome DevTools
11# Open chrome://inspect in Chrome
12# Click "inspect" under Remote Target1// Programmatic breakpoints
2function processData(data) {
3 debugger; // Pauses here when inspector connected
4 return transform(data);
5}
6
7// Conditional breakpoint in DevTools
8// Right-click line -> Add conditional breakpoint
9// Enter condition: data.length > 100VS Code Debugging#
1// .vscode/launch.json
2{
3 "version": "0.2.0",
4 "configurations": [
5 {
6 "name": "Debug Current File",
7 "type": "node",
8 "request": "launch",
9 "program": "${file}",
10 "skipFiles": ["<node_internals>/**"]
11 },
12 {
13 "name": "Debug Application",
14 "type": "node",
15 "request": "launch",
16 "program": "${workspaceFolder}/src/index.ts",
17 "preLaunchTask": "tsc: build",
18 "outFiles": ["${workspaceFolder}/dist/**/*.js"],
19 "env": {
20 "NODE_ENV": "development"
21 }
22 },
23 {
24 "name": "Debug Tests",
25 "type": "node",
26 "request": "launch",
27 "program": "${workspaceFolder}/node_modules/jest/bin/jest",
28 "args": ["--runInBand", "--no-cache"],
29 "console": "integratedTerminal"
30 },
31 {
32 "name": "Attach to Process",
33 "type": "node",
34 "request": "attach",
35 "port": 9229
36 }
37 ]
38}Error Handling Debug#
1// Catch uncaught exceptions
2process.on('uncaughtException', (error) => {
3 console.error('Uncaught Exception:', error);
4 console.error('Stack:', error.stack);
5 process.exit(1);
6});
7
8// Catch unhandled promise rejections
9process.on('unhandledRejection', (reason, promise) => {
10 console.error('Unhandled Rejection at:', promise);
11 console.error('Reason:', reason);
12});
13
14// Async stack traces (Node 12+)
15// Run with: node --async-stack-traces app.js
16
17// Error with cause
18function fetchUser(id: string) {
19 try {
20 return db.query(`SELECT * FROM users WHERE id = $1`, [id]);
21 } catch (error) {
22 throw new Error(`Failed to fetch user ${id}`, { cause: error });
23 }
24}
25
26// Log full error chain
27function logErrorChain(error: Error, depth = 0) {
28 console.error(' '.repeat(depth) + error.message);
29 if (error.cause) {
30 logErrorChain(error.cause as Error, depth + 1);
31 }
32}Memory Debugging#
1// Check memory usage
2function logMemory() {
3 const used = process.memoryUsage();
4 console.log({
5 heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
6 heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`,
7 external: `${Math.round(used.external / 1024 / 1024)} MB`,
8 rss: `${Math.round(used.rss / 1024 / 1024)} MB`,
9 });
10}
11
12// Periodic memory logging
13setInterval(logMemory, 5000);
14
15// Heap snapshot
16const v8 = require('v8');
17const fs = require('fs');
18
19function takeHeapSnapshot() {
20 const snapshotStream = v8.writeHeapSnapshot();
21 console.log('Heap snapshot written to:', snapshotStream);
22}
23
24// Trigger with signal
25process.on('SIGUSR2', takeHeapSnapshot);
26
27// Force garbage collection (run with --expose-gc)
28if (global.gc) {
29 global.gc();
30 console.log('Garbage collection triggered');
31}1# Run with memory debugging
2node --expose-gc --max-old-space-size=4096 app.js
3
4# Generate heap snapshot on OOM
5node --heap-prof app.js
6
7# Analyze with Chrome DevTools Memory tabPerformance Profiling#
1// Built-in profiler
2const { performance, PerformanceObserver } = require('perf_hooks');
3
4// Measure function execution
5performance.mark('start');
6await expensiveOperation();
7performance.mark('end');
8performance.measure('operation', 'start', 'end');
9
10// Observer for measurements
11const obs = new PerformanceObserver((items) => {
12 items.getEntries().forEach((entry) => {
13 console.log(`${entry.name}: ${entry.duration}ms`);
14 });
15});
16obs.observe({ entryTypes: ['measure'] });
17
18// Wrap function for timing
19function withTiming<T>(name: string, fn: () => T): T {
20 const start = performance.now();
21 try {
22 return fn();
23 } finally {
24 console.log(`${name}: ${(performance.now() - start).toFixed(2)}ms`);
25 }
26}
27
28// Async version
29async function withTimingAsync<T>(
30 name: string,
31 fn: () => Promise<T>
32): Promise<T> {
33 const start = performance.now();
34 try {
35 return await fn();
36 } finally {
37 console.log(`${name}: ${(performance.now() - start).toFixed(2)}ms`);
38 }
39}1# CPU profiling
2node --prof app.js
3node --prof-process isolate-*.log > profile.txt
4
5# Generate flamegraph
6node --perf-basic-prof app.js
7# Use perf and flamegraph toolsDebug Library#
1import debug from 'debug';
2
3// Create namespaced loggers
4const logApp = debug('app');
5const logDb = debug('app:db');
6const logApi = debug('app:api');
7const logAuth = debug('app:auth');
8
9// Usage
10logApp('Application starting');
11logDb('Connected to database');
12logApi('Request received: %O', { method: 'GET', path: '/users' });
13logAuth('User authenticated: %s', userId);
14
15// Enable via environment variable
16// DEBUG=app:* node app.js // All app logs
17// DEBUG=app:db node app.js // Only db logs
18// DEBUG=app:db,app:api node app.js // Multiple
19// DEBUG=* node app.js // Everything
20
21// In code
22debug.enable('app:db');
23debug.disable('app:api');Network Debugging#
1// Log all HTTP requests
2import http from 'http';
3
4const originalRequest = http.request;
5http.request = function (options, callback) {
6 console.log('HTTP Request:', {
7 method: options.method || 'GET',
8 host: options.host || options.hostname,
9 path: options.path,
10 });
11 return originalRequest.apply(this, arguments);
12};
13
14// Axios interceptors
15import axios from 'axios';
16
17axios.interceptors.request.use((config) => {
18 console.log('Request:', config.method?.toUpperCase(), config.url);
19 return config;
20});
21
22axios.interceptors.response.use(
23 (response) => {
24 console.log('Response:', response.status, response.config.url);
25 return response;
26 },
27 (error) => {
28 console.error('Request failed:', error.message);
29 return Promise.reject(error);
30 }
31);Best Practices#
General:
✓ Use structured logging
✓ Add context to errors
✓ Use debug namespaces
✓ Remove debug code before production
Tools:
✓ Master VS Code debugger
✓ Learn Chrome DevTools
✓ Use source maps
✓ Profile before optimizing
Memory:
✓ Monitor memory usage
✓ Take heap snapshots
✓ Check for event listener leaks
✓ Review closure references
Conclusion#
Effective debugging combines multiple techniques. Use console methods for quick checks, the inspector for complex issues, and profiling tools for performance. Structure your logging with namespaces and levels to quickly isolate problems in production.