Node.js Inspector
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
- Node.js Inspector Official Documentation
- Chrome DevTools Node.js
- VS Code Node.js Debugging
- Node.js Debugging Guide
- Inspector API Documentation
- Node.js Profiling Guide
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);