Node.js Inspector

DebuggingNode.jsJavaScriptChrome DevToolsServer-sideRemote Debugging

Debugging Tool

Node.js Inspector

Overview

Node.js Inspector is the built-in debugger for Node.js. It integrates with Chrome DevTools, enabling server-side JavaScript debugging and supports remote debugging capabilities.

Details

Node.js Inspector is a debugging feature built into Node.js 6.3 and later, seamlessly integrating with Chrome/Chromium browsers using the Chrome DevTools Protocol. It was standardized to replace the legacy node-inspector package (now deprecated), providing professional-grade debugging capabilities without additional installation.

The key feature of Inspector is the ability to debug server-side JavaScript using Chrome DevTools in the browser. This enables consistent debugging experience across frontend and backend development. Real-time communication via WebSocket connection provides rich functionality including breakpoint setting, variable monitoring, step execution, performance analysis, and memory profiling.

Integration with major IDEs like VS Code, IntelliJ IDEA, and WebStorm has advanced, allowing debugging functionality within respective editors. VS Code's Node.js debugging support is particularly excellent, with flexible debug configuration through configuration files (launch.json), auto-attach functionality, and TypeScript source map support, making it a standard tool in modern Node.js development.

Debugging is also possible in Docker environments, Kubernetes, AWS Lambda, Azure Functions, and other cloud environments, with remote debugging capabilities enabling problem investigation in production-like environments. It also provides advanced features for Node.js-specific issues such as CPU profiling, heap snapshots, and async stack traces.

Since Node.js 12, continuous feature enhancements include detailed control through --inspect-brk and --inspect-port options, worker_threads debugging support, and full ES Modules (ESM) support.

Pros and Cons

Pros

  • Built-in Standard: Built into Node.js, no additional installation required
  • Chrome DevTools Integration: Familiar debugging interface
  • Remote Debugging: Network-based debugging capabilities
  • IDE Integration: Excellent integration with VS Code, WebStorm, etc.
  • Rich Features: Profiling, memory analysis, async debugging
  • TypeScript Support: Full TypeScript support via source maps
  • Docker Support: Easy debugging in container environments
  • Free Usage: Open source with commercial use available

Cons

  • Performance Impact: Execution speed degradation when debugging enabled
  • Security Risk: Dangerous to enable Inspector in production environments
  • Port Management: Proper management of debug ports required
  • Learning Curve: Need to master Chrome DevTools
  • Network Dependency: Network quality impact during remote debugging
  • Memory Consumption: Increased memory usage due to debug information
  • Version Dependency: Feature differences across Node.js versions

Key Links

Usage Examples

Basic Inspector Startup

# Basic Inspector startup (default port 9229)
node --inspect app.js

# Break on startup
node --inspect-brk app.js

# Custom port specification
node --inspect=9230 app.js

# Specific host and port startup
node --inspect=localhost:9229 app.js

# Listen on all network interfaces (dangerous: avoid in production)
node --inspect=0.0.0.0:9229 app.js

# Connection URL after Inspector startup:
# Shown at chrome://inspect
# Or directly: chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/uuid

Sample Application

// app.js - Node.js application for debugging
const express = require('express');
const app = express();
const port = 3000;

// Middleware
app.use(express.json());

// Route handling (breakpoint candidates)
app.get('/', (req, res) => {
    // Set breakpoint here
    const message = 'Hello World!';
    const timestamp = new Date().toISOString();
    
    res.json({ 
        message, 
        timestamp,
        nodeVersion: process.version 
    });
});

// Async processing debug example
app.get('/async', async (req, res) => {
    try {
        // Debug async functions
        const data = await fetchDataFromAPI();
        const processed = processData(data);
        
        res.json({ result: processed });
    } catch (error) {
        // Error handling debugging
        console.error('Error in async handler:', error);
        res.status(500).json({ error: error.message });
    }
});

// Database operation debug example
app.post('/users', async (req, res) => {
    const { name, email } = req.body;
    
    // Validation (debug point)
    if (!name || !email) {
        return res.status(400).json({ 
            error: 'Name and email are required' 
        });
    }
    
    try {
        // Database operation (debug target)
        const user = await createUser({ name, email });
        res.status(201).json(user);
    } catch (error) {
        res.status(500).json({ error: 'Failed to create user' });
    }
});

// Heavy processing profiling target
app.get('/heavy', (req, res) => {
    const start = Date.now();
    
    // CPU-intensive processing
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i);
    }
    
    const duration = Date.now() - start;
    res.json({ result, duration });
});

// Helper functions
async function fetchDataFromAPI() {
    // External API call simulation
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ data: 'sample data', count: 10 });
        }, 100);
    });
}

function processData(data) {
    // Data processing logic
    return {
        ...data,
        processed: true,
        processedAt: new Date().toISOString()
    };
}

async function createUser(userData) {
    // Database operation simulation
    const user = {
        id: Math.random().toString(36).substr(2, 9),
        ...userData,
        createdAt: new Date().toISOString()
    };
    
    // Pseudo database save processing
    await new Promise(resolve => setTimeout(resolve, 50));
    
    return user;
}

// Server startup
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
    console.log('Inspector ready - Open chrome://inspect to debug');
});

// Execution commands:
// node --inspect app.js
// or
// node --inspect-brk app.js  # Stop on startup

VS Code Debug Configuration

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Program",
            "type": "node",
            "request": "launch",
            "program": "${workspaceFolder}/app.js",
            "env": {
                "NODE_ENV": "development"
            },
            "args": [],
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen"
        },
        {
            "name": "Launch with Inspector",
            "type": "node",
            "request": "launch",
            "program": "${workspaceFolder}/app.js",
            "port": 9229,
            "restart": true,
            "env": {
                "NODE_ENV": "development"
            }
        },
        {
            "name": "Attach to Process",
            "type": "node",
            "request": "attach",
            "port": 9229,
            "restart": true,
            "localRoot": "${workspaceFolder}",
            "remoteRoot": "/app"  // For Docker container
        },
        {
            "name": "Jest Tests",
            "type": "node",
            "request": "launch",
            "program": "${workspaceFolder}/node_modules/.bin/jest",
            "args": ["--runInBand"],
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen"
        }
    ]
}

TypeScript Debug Configuration

// tsconfig.json - TypeScript configuration
{
    "compilerOptions": {
        "target": "ES2020",
        "module": "commonjs",
        "sourceMap": true,  // Enable source map generation
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist"]
}
// src/app.ts - TypeScript sample
interface User {
    id: string;
    name: string;
    email: string;
    createdAt: Date;
}

class UserService {
    private users: User[] = [];
    
    async createUser(userData: Omit<User, 'id' | 'createdAt'>): Promise<User> {
        // Debug point: TypeScript type information also verifiable
        const user: User = {
            id: this.generateId(),
            name: userData.name,
            email: userData.email,
            createdAt: new Date()
        };
        
        // Validation
        if (!this.isValidEmail(user.email)) {
            throw new Error('Invalid email format');
        }
        
        this.users.push(user);
        return user;
    }
    
    private generateId(): string {
        return Math.random().toString(36).substr(2, 9);
    }
    
    private isValidEmail(email: string): boolean {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
    
    getUsers(): User[] {
        return [...this.users];  // defensive copy
    }
}

// Express application
import express from 'express';

const app = express();
const userService = new UserService();

app.use(express.json());

app.post('/users', async (req, res) => {
    try {
        // Debug with TypeScript type checking benefits
        const user = await userService.createUser(req.body);
        res.status(201).json(user);
    } catch (error) {
        if (error instanceof Error) {
            res.status(400).json({ error: error.message });
        } else {
            res.status(500).json({ error: 'Unknown error' });
        }
    }
});

app.listen(3000, () => {
    console.log('TypeScript server running on port 3000');
});

// VS Code launch.json for TypeScript
/*
{
    "name": "TypeScript Debug",
    "type": "node",
    "request": "launch",
    "program": "${workspaceFolder}/src/app.ts",
    "preLaunchTask": "tsc: build - tsconfig.json",
    "outFiles": ["${workspaceFolder}/dist/**/*.js"],
    "env": {
        "NODE_ENV": "development"
    }
}
*/

Docker Environment Debugging

# Dockerfile
FROM node:18-alpine

WORKDIR /app

# package files
COPY package*.json ./
RUN npm ci --only=production

# app source
COPY . .

# Expose Inspector port
EXPOSE 3000 9229

# Start in debug mode
CMD ["node", "--inspect=0.0.0.0:9229", "app.js"]
# docker-compose.yml
version: '3.8'
services:
  node-app:
    build: .
    ports:
      - "3000:3000"
      - "9229:9229"  # Inspector port
    environment:
      - NODE_ENV=development
    volumes:
      - .:/app
      - /app/node_modules
    command: node --inspect=0.0.0.0:9229 app.js
# Docker debug commands
# Start container
docker-compose up

# Attach to running container
docker exec -it container_name node --inspect=0.0.0.0:9229 app.js

# VS Code connection launch.json configuration
# "remoteRoot": "/app",
# "localRoot": "${workspaceFolder}"

Advanced Debugging Techniques

// advanced-debugging.js
const { performance } = require('perf_hooks');

class AdvancedDebuggingExample {
    constructor() {
        this.data = new Map();
        this.metrics = [];
    }
    
    // Method with performance measurement
    async processWithMetrics(items) {
        const start = performance.now();
        
        try {
            // Async processing debugging
            const results = await Promise.all(
                items.map(async (item, index) => {
                    // Conditional breakpoint setting point
                    if (index === 5) {
                        debugger; // Programmatic breakpoint
                    }
                    
                    return await this.processItem(item);
                })
            );
            
            const end = performance.now();
            this.recordMetric('processWithMetrics', end - start);
            
            return results;
        } catch (error) {
            // Debug information during error handling
            console.error('Processing failed:', {
                error: error.message,
                stack: error.stack,
                items: items.length
            });
            throw error;
        }
    }
    
    async processItem(item) {
        // CPU-intensive processing simulation
        const result = await new Promise(resolve => {
            setImmediate(() => {
                const processed = {
                    original: item,
                    hash: this.calculateHash(item),
                    timestamp: Date.now()
                };
                resolve(processed);
            });
        });
        
        this.data.set(item.id, result);
        return result;
    }
    
    calculateHash(item) {
        // Heavy calculation processing (profiling target)
        let hash = 0;
        const str = JSON.stringify(item);
        
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash; // 32bit integer
        }
        
        return hash;
    }
    
    recordMetric(operation, duration) {
        this.metrics.push({
            operation,
            duration,
            timestamp: Date.now(),
            memoryUsage: process.memoryUsage()
        });
    }
    
    getMetrics() {
        return this.metrics;
    }
    
    // Helper for memory leak detection
    simulateMemoryLeak() {
        const leakyArray = [];
        
        setInterval(() => {
            // Intentional memory leak
            leakyArray.push(new Array(1000).fill('leak'));
            
            if (leakyArray.length > 100) {
                // Leak detectable with Inspector Heap Snapshot
                console.log('Memory leak simulation running...', 
                    process.memoryUsage());
            }
        }, 100);
    }
}

// Usage example
async function main() {
    const debugExample = new AdvancedDebuggingExample();
    
    // Test data
    const items = Array.from({ length: 10 }, (_, i) => ({
        id: `item-${i}`,
        value: Math.random() * 100,
        category: ['A', 'B', 'C'][i % 3]
    }));
    
    try {
        // Main debug processing
        const results = await debugExample.processWithMetrics(items);
        
        // Metrics verification (display in Inspector Console)
        console.table(debugExample.getMetrics());
        
        return results;
    } catch (error) {
        console.error('Main process failed:', error);
    }
}

// Inspector startup: node --inspect-brk advanced-debugging.js
main().catch(console.error);

Profiling and Memory Analysis

// profiling-example.js
const fs = require('fs').promises;
const { performance, PerformanceObserver } = require('perf_hooks');

// Performance Observer configuration
const perfObserver = new PerformanceObserver((items) => {
    items.getEntries().forEach((entry) => {
        console.log(`${entry.name}: ${entry.duration}ms`);
    });
});
perfObserver.observe({ entryTypes: ['measure', 'function'] });

class ProfilingExample {
    async fileProcessingWorkload() {
        // CPU Profiler analysis target
        performance.mark('start-file-processing');
        
        try {
            // Large file operations
            const files = await this.generateTestFiles();
            const results = await this.processFiles(files);
            
            performance.mark('end-file-processing');
            performance.measure('file-processing', 
                'start-file-processing', 'end-file-processing');
            
            return results;
        } catch (error) {
            console.error('File processing failed:', error);
            throw error;
        }
    }
    
    async generateTestFiles() {
        const files = [];
        
        for (let i = 0; i < 100; i++) {
            const content = this.generateRandomContent(1000);
            const filename = `/tmp/test-${i}.txt`;
            
            await fs.writeFile(filename, content);
            files.push(filename);
        }
        
        return files;
    }
    
    generateRandomContent(size) {
        // CPU-intensive processing
        let content = '';
        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        
        for (let i = 0; i < size; i++) {
            content += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        
        return content;
    }
    
    async processFiles(files) {
        const results = [];
        
        // Parallel processing profiling
        const chunks = this.chunkArray(files, 10);
        
        for (const chunk of chunks) {
            const chunkResults = await Promise.all(
                chunk.map(file => this.processFile(file))
            );
            results.push(...chunkResults);
        }
        
        return results;
    }
    
    async processFile(filename) {
        performance.mark(`start-${filename}`);
        
        try {
            const content = await fs.readFile(filename, 'utf8');
            const processed = this.analyzeContent(content);
            
            // File deletion
            await fs.unlink(filename);
            
            performance.mark(`end-${filename}`);
            performance.measure(`process-${filename}`, 
                `start-${filename}`, `end-${filename}`);
            
            return processed;
        } catch (error) {
            console.error(`Failed to process ${filename}:`, error);
            return null;
        }
    }
    
    analyzeContent(content) {
        // String analysis processing (profiling target)
        const words = content.split(/\s+/);
        const wordCount = words.length;
        const charCount = content.length;
        const uniqueWords = new Set(words.map(w => w.toLowerCase()));
        
        return {
            wordCount,
            charCount,
            uniqueWordCount: uniqueWords.size,
            averageWordLength: charCount / wordCount
        };
    }
    
    chunkArray(array, chunkSize) {
        const chunks = [];
        for (let i = 0; i < array.length; i += chunkSize) {
            chunks.push(array.slice(i, i + chunkSize));
        }
        return chunks;
    }
}

// CPU Profiler execution
// 1. Start app with node --inspect app.js
// 2. Chrome DevTools → Profiler → Start CPU profiling
// 3. Execute code below
// 4. Stop profiling to check results

async function runProfiling() {
    const profiler = new ProfilingExample();
    
    console.log('Starting profiling workload...');
    console.time('total-execution');
    
    const results = await profiler.fileProcessingWorkload();
    
    console.timeEnd('total-execution');
    console.log(`Processed ${results.length} files`);
    
    // Check memory usage with Heap Snapshot
    if (global.gc) {
        global.gc(); // Execute garbage collection
    }
    
    console.log('Memory usage:', process.memoryUsage());
}

// Execution command:
// node --inspect --expose-gc profiling-example.js
runProfiling().catch(console.error);