Part 12 – Error Handling and Debugging

Proper error handling and debugging are essential skills for Node.js developers. This guide covers everything from basic error patterns to advanced debugging techniques.

1. Node.js Error Types

Built-in Error Types

  • Error: Generic error constructor
  • SyntaxError: Parsing errors
  • ReferenceError: Undefined variables
  • TypeError: Wrong type operations
  • RangeError: Out-of-range values

Common Operational Errors

  • Failed I/O operations
  • Network connectivity issues
  • Database query failures
  • Invalid user input
  • Resource exhaustion

2. Basic Error Handling Patterns

Try/Catch Blocks

try {
    // Synchronous code that might throw
    JSON.parse(invalidJson);
} catch (err) {
    console.error('Parsing failed:', err.message);
    // Handle or rethrow
}

Error-First Callbacks

fs.readFile('nonexistent.txt', (err, data) => {
    if (err) {
        if (err.code === 'ENOENT') {
            console.error('File not found');
        } else {
            console.error('Unknown error:', err);
        }
        return;
    }
    // Process data
});

Promise Error Handling

fetchData()
    .then(data => processData(data))
    .catch(err => {
        console.error('Processing failed:', err);
        // Handle or rethrow
    });

3. Advanced Error Handling

Custom Error Classes

class AppError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.statusCode = statusCode;
        this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
        this.isOperational = true;
        Error.captureStackTrace(this, this.constructor);
    }
}

// Usage
throw new AppError('Invalid input', 400);

Global Error Handler

// Express error handling middleware
app.use((err, req, res, next) => {
    err.statusCode = err.statusCode || 500;
    err.status = err.status || 'error';

    if (process.env.NODE_ENV === 'development') {
        res.status(err.statusCode).json({
            status: err.status,
            error: err,
            message: err.message,
            stack: err.stack
        });
    } else {
        // Production: send minimal info
        res.status(err.statusCode).json({
            status: err.status,
            message: err.message
        });
    }
});

4. Debugging Techniques

Console Debugging

// Debug with labels and objects
console.log('[DEBUG] User data:', { id: user.id, email: user.email });

// Time operations
console.time('dbQuery');
await User.find();
console.timeEnd('dbQuery');

Node.js Debugger

// Start app in debug mode
node inspect app.js

// Chrome DevTools
chrome://inspect

// VS Code debugging
// Add to launch.json:
{
    "type": "node",
    "request": "launch",
    "name": "Debug App",
    "program": "${workspaceFolder}/app.js"
}

Debugging Async Code

// Use async_hooks for complex flows
const async_hooks = require('async_hooks');

const asyncHook = async_hooks.createHook({
    init(asyncId, type, triggerAsyncId) {
        console.log(`Async ${type} started`);
    },
    destroy(asyncId) {
        console.log(`Async ${asyncId} destroyed`);
    }
});
asyncHook.enable();

5. Logging Strategies

Structured Logging

const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transports.File({ 
            filename: 'error.log', 
            level: 'error' 
        }),
        new winston.transports.File({ 
            filename: 'combined.log' 
        })
    ]
});

// Usage
logger.error('Database connection failed', { 
    error: err.message,
    timestamp: new Date() 
});

Logging Middleware

app.use((req, res, next) => {
    const start = Date.now();

    res.on('finish', () => {
        logger.info({
            method: req.method,
            url: req.url,
            status: res.statusCode,
            responseTime: `${Date.now() - start}ms`,
            ip: req.ip
        });
    });

    next();
});

6. Production Error Monitoring

Process Management

// Handle uncaught exceptions
process.on('uncaughtException', err => {
    logger.error('Uncaught Exception:', err);
    // Perform cleanup
    process.exit(1); // Mandatory (according to Node.js docs)
});

// Handle unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
    logger.error('Unhandled Rejection:', reason);
    // Can choose to exit or continue
});

Monitoring Tools

  • Sentry: Error tracking
  • New Relic: Performance monitoring
  • Datadog: Full-stack observability
  • ELK Stack: Log analysis

7. Performance Debugging

CPU Profiling

// Start with --inspect flag
node --inspect app.js

// Take CPU snapshot
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('Profiler.enable', () => {
    session.post('Profiler.start', () => {
        // Run operations to profile
        setTimeout(() => {
            session.post('Profiler.stop', (err, { profile }) => {
                require('fs').writeFileSync('profile.cpuprofile', 
                    JSON.stringify(profile));
            });
        }, 10000);
    });
});

Memory Leak Detection

const heapdump = require('heapdump');

// Trigger heap dump on signal
process.on('SIGUSR2', () => {
    const filename = `heapdump-${Date.now()}.heapsnapshot`;
    heapdump.writeSnapshot(filename, err => {
        if (err) console.error(err);
        else console.log(`Heap dump written to ${filename}`);
    });
});

// Or use --heapsnapshot-signal flag
node --heapsnapshot-signal=SIGUSR2 app.js

Next: Testing Node.js Applications →

Debugging Checklist

  • ✅ Reproduce the issue consistently
  • ✅ Check application logs
  • ✅ Isolate the problematic code
  • ✅ Verify inputs and outputs
  • ✅ Check for resource leaks
  • ✅ Profile performance bottlenecks

Leave a Comment

Your email address will not be published. Required fields are marked *