Full-Stack Architecture Patterns

Master architecture patterns for building scalable full-stack applications.

intermediate Backend Development 6 hours

Chapter 10: Error Handling

Chapter 10 of 15

Chapter 10: Error Handling

10.1 Error Handling Patterns

Proper error handling ensures applications fail gracefully and provide useful feedback. Implement consistent error handling across all layers.

Error Types:

  • Client Errors (4xx): Invalid requests, authentication failures
  • Server Errors (5xx): Application failures, database errors
  • Network Errors: Timeouts, connection failures
  • Validation Errors: Invalid input data

Error Handling Strategies:

  • Try-Catch Blocks: Handle synchronous errors
  • Promise Rejection: Handle asynchronous errors
  • Error Middleware: Centralized error handling
  • Error Boundaries: React component error handling
// Express error middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    
    res.status(err.status || 500).json({
        error: {
            message: err.message || 'Internal Server Error',
            code: err.code || 'INTERNAL_ERROR',
            ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
        }
    });
});

// Usage
app.get('/users/:id', async (req, res, next) => {
    try {
        const user = await User.findById(req.params.id);
        if (!user) {
            const error = new Error('User not found');
            error.status = 404;
            throw error;
        }
        res.json(user);
    } catch (err) {
        next(err);
    }
});

Error Response Format:

{
    "error": {
        "code": "USER_NOT_FOUND",
        "message": "User with ID 123 does not exist",
        "details": {
            "userId": 123
        }
    }
}

10.2 Logging and Monitoring

Logging and monitoring help track errors, debug issues, and understand application behavior.

Logging Levels:

  • DEBUG: Detailed information for debugging
  • INFO: General informational messages
  • WARN: Warning messages for potential issues
  • ERROR: Error messages for failures
  • FATAL: Critical errors that may stop application
// Structured logging
logger.info('User logged in', {
    userId: user.id,
    email: user.email,
    ip: req.ip,
    timestamp: new Date()
});

logger.error('Database connection failed', {
    error: err.message,
    stack: err.stack,
    database: 'users_db'
});

Logging Best Practices:

  • Use structured logging (JSON format)
  • Include context (user ID, request ID, timestamp)
  • Don't log sensitive information (passwords, tokens)
  • Use appropriate log levels
  • Centralize logs for analysis

Monitoring Tools:

  • Application Monitoring: New Relic, Datadog, AppDynamics
  • Error Tracking: Sentry, Rollbar, Bugsnag
  • Log Aggregation: ELK Stack, Splunk, CloudWatch
  • Performance Monitoring: APM tools, custom metrics

10.3 Error Recovery

Implement strategies to recover from errors gracefully.

Retry Logic:

  • Retry transient failures
  • Exponential backoff
  • Maximum retry attempts
  • Good for network and database errors
async function retryOperation(operation, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await operation();
        } catch (err) {
            if (i === maxRetries - 1) throw err;
            await sleep(Math.pow(2, i) * 1000); // Exponential backoff
        }
    }
}

Circuit Breaker Pattern:

  • Prevent cascading failures
  • Stop calling failing service
  • Return cached data or default response
  • Automatically retry after timeout

10.4 User-Friendly Error Messages

Present errors to users in a clear, helpful way.

  • Don't expose technical details to users
  • Provide actionable error messages
  • Suggest solutions when possible
  • Use appropriate HTTP status codes
  • Log detailed errors server-side