+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 211 of 355

๐Ÿ“˜ Error Handling: Global Error Handler

Master error handling: global error handler in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand global error handling fundamentals ๐ŸŽฏ
  • Apply error handling in real projects ๐Ÿ—๏ธ
  • Debug common error handling issues ๐Ÿ›
  • Write type-safe error handling code โœจ

๐ŸŽฏ Introduction

Welcome to this essential tutorial on global error handling in TypeScript! ๐ŸŽ‰ In this guide, weโ€™ll explore how to catch and handle errors gracefully across your entire application.

Youโ€™ll discover how global error handlers can transform your backend development experience. Whether youโ€™re building Express APIs ๐ŸŒ, microservices ๐Ÿ–ฅ๏ธ, or complex server applications ๐Ÿ“š, understanding error handling is crucial for building robust, maintainable applications.

By the end of this tutorial, youโ€™ll feel confident implementing comprehensive error handling in your TypeScript projects! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Global Error Handling

๐Ÿค” What is Global Error Handling?

Global error handling is like having a safety net for your entire application ๐Ÿ•ธ๏ธ. Think of it as a friendly guardian that catches any errors that slip through your normal error handling, ensuring your app doesnโ€™t crash unexpectedly.

In TypeScript terms, a global error handler is a centralized system that catches unhandled errors, processes them consistently, and responds appropriately ๐Ÿ›ก๏ธ. This means you can:

  • โœจ Prevent application crashes
  • ๐Ÿš€ Log errors consistently
  • ๐Ÿ›ก๏ธ Provide user-friendly error responses
  • ๐Ÿ“Š Monitor application health

๐Ÿ’ก Why Use Global Error Handling?

Hereโ€™s why developers love global error handlers:

  1. Crash Prevention ๐Ÿ”’: Keep your app running even when unexpected errors occur
  2. Consistent Logging ๐Ÿ’ป: All errors get logged in the same format
  3. Better User Experience ๐Ÿ“–: Users see friendly messages instead of stack traces
  4. Debugging Made Easy ๐Ÿ”ง: Centralized error information helps you fix issues faster

Real-world example: Imagine building an e-commerce API ๐Ÿ›’. With global error handling, a database connection error wonโ€™t crash your entire server - instead, users get a friendly โ€œPlease try again laterโ€ message while you get detailed logs to fix the issue.

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Express Error Handler

Letโ€™s start with a friendly example:

// ๐Ÿ‘‹ Hello, Express error handling!
import express, { Request, Response, NextFunction } from 'express';

const app = express();

// ๐ŸŽจ Creating a simple error interface
interface AppError extends Error {
  statusCode?: number;    // ๐Ÿšจ HTTP status code
  isOperational?: boolean; // ๐ŸŽฏ Is this a known error?
}

// ๐Ÿ›ก๏ธ Global error handler middleware
const globalErrorHandler = (
  err: AppError,
  req: Request,
  res: Response,
  next: NextFunction
): void => {
  // ๐Ÿ“ Log the error details
  console.error('๐Ÿšจ Error caught:', err.message);
  console.error('๐Ÿ“ Stack trace:', err.stack);
  
  // ๐ŸŽฏ Send user-friendly response
  const statusCode = err.statusCode || 500;
  const message = err.isOperational ? err.message : 'Something went wrong! ๐Ÿ˜ฐ';
  
  res.status(statusCode).json({
    success: false,
    message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
};

// ๐Ÿ”ง Register the error handler (must be last!)
app.use(globalErrorHandler);

๐Ÿ’ก Explanation: Notice how we use a custom AppError interface and provide different responses for development vs production! The error handler must be registered last in Express.

๐ŸŽฏ Common Patterns

Here are patterns youโ€™ll use daily:

// ๐Ÿ—๏ธ Pattern 1: Custom error classes
class ValidationError extends Error {
  public readonly statusCode = 400;
  public readonly isOperational = true;
  
  constructor(message: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

class DatabaseError extends Error {
  public readonly statusCode = 500;
  public readonly isOperational = true;
  
  constructor(message: string = 'Database operation failed ๐Ÿ’พ') {
    super(message);
    this.name = 'DatabaseError';
  }
}

// ๐ŸŽจ Pattern 2: Error factory
const createError = (message: string, statusCode: number = 500): AppError => {
  const error = new Error(message) as AppError;
  error.statusCode = statusCode;
  error.isOperational = true;
  return error;
};

// ๐Ÿ”„ Pattern 3: Async error wrapper
const asyncHandler = (fn: Function) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce API Error Handler

Letโ€™s build something real:

// ๐Ÿ›๏ธ Define our error types
interface APIError extends Error {
  statusCode: number;
  isOperational: boolean;
  errorCode?: string; // ๐Ÿท๏ธ Custom error codes
}

// ๐Ÿ›’ E-commerce specific errors
class ProductNotFoundError extends Error implements APIError {
  public readonly statusCode = 404;
  public readonly isOperational = true;
  public readonly errorCode = 'PRODUCT_NOT_FOUND';
  
  constructor(productId: string) {
    super(`Product with ID ${productId} not found ๐Ÿ”`);
    this.name = 'ProductNotFoundError';
  }
}

class InsufficientStockError extends Error implements APIError {
  public readonly statusCode = 400;
  public readonly isOperational = true;
  public readonly errorCode = 'INSUFFICIENT_STOCK';
  
  constructor(available: number, requested: number) {
    super(`Only ${available} items available, but ${requested} requested ๐Ÿ“ฆ`);
    this.name = 'InsufficientStockError';
  }
}

// ๐ŸŽฏ Comprehensive error handler
class ErrorHandler {
  // ๐Ÿšจ Main error handling method
  public static handleError(
    err: APIError,
    req: Request,
    res: Response,
    next: NextFunction
  ): void {
    // ๐Ÿ“ Log error details
    ErrorHandler.logError(err, req);
    
    // ๐ŸŽจ Format error response
    const errorResponse = ErrorHandler.formatErrorResponse(err);
    
    // ๐Ÿ“ค Send response
    res.status(err.statusCode || 500).json(errorResponse);
  }
  
  // ๐Ÿ“‹ Log error with context
  private static logError(err: APIError, req: Request): void {
    const errorInfo = {
      timestamp: new Date().toISOString(),
      method: req.method,
      url: req.url,
      userAgent: req.get('User-Agent'),
      ip: req.ip,
      error: {
        name: err.name,
        message: err.message,
        stack: err.stack,
        statusCode: err.statusCode,
        errorCode: err.errorCode
      }
    };
    
    // ๐Ÿ” Different log levels based on error severity
    if (err.statusCode >= 500) {
      console.error('๐Ÿšจ CRITICAL ERROR:', JSON.stringify(errorInfo, null, 2));
    } else {
      console.warn('โš ๏ธ CLIENT ERROR:', JSON.stringify(errorInfo, null, 2));
    }
  }
  
  // ๐ŸŽจ Format consistent error responses
  private static formatErrorResponse(err: APIError) {
    const isProduction = process.env.NODE_ENV === 'production';
    
    return {
      success: false,
      error: {
        message: err.isOperational ? err.message : 'Internal server error ๐Ÿ˜ฐ',
        code: err.errorCode || 'UNKNOWN_ERROR',
        statusCode: err.statusCode || 500,
        timestamp: new Date().toISOString(),
        // ๐Ÿ”ง Include stack trace only in development
        ...((!isProduction && err.stack) && { stack: err.stack })
      }
    };
  }
}

// ๐ŸŽฎ Let's use it in routes!
app.get('/products/:id', asyncHandler(async (req: Request, res: Response) => {
  const { id } = req.params;
  
  // ๐Ÿ” Simulate product lookup
  const product = await findProductById(id);
  if (!product) {
    throw new ProductNotFoundError(id);
  }
  
  res.json({ success: true, data: product });
}));

app.post('/cart/add', asyncHandler(async (req: Request, res: Response) => {
  const { productId, quantity } = req.body;
  
  // ๐Ÿ“ฆ Check stock availability
  const stock = await getProductStock(productId);
  if (stock < quantity) {
    throw new InsufficientStockError(stock, quantity);
  }
  
  // โœ… Add to cart logic here
  res.json({ success: true, message: 'Item added to cart! ๐Ÿ›’' });
}));

// ๐Ÿ›ก๏ธ Register our error handler
app.use(ErrorHandler.handleError);

๐ŸŽฏ Try it yourself: Add a PaymentFailedError class and handle credit card processing failures!

๐ŸŽฎ Example 2: Game Server Error Handling

Letโ€™s make error handling fun:

// ๐Ÿ† Game-specific error types
enum GameErrorCode {
  PLAYER_NOT_FOUND = 'PLAYER_NOT_FOUND',
  GAME_FULL = 'GAME_FULL',
  INVALID_MOVE = 'INVALID_MOVE',
  GAME_OVER = 'GAME_OVER',
  CONNECTION_LOST = 'CONNECTION_LOST'
}

interface GameError extends Error {
  code: GameErrorCode;
  playerId?: string;
  gameId?: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
}

class GameErrorHandler {
  // ๐ŸŽฎ Handle game-specific errors
  public static handleGameError(error: GameError): void {
    // ๐Ÿ“Š Log based on severity
    switch (error.severity) {
      case 'critical':
        console.error('๐Ÿšจ CRITICAL GAME ERROR:', {
          code: error.code,
          message: error.message,
          playerId: error.playerId,
          gameId: error.gameId,
          timestamp: new Date().toISOString()
        });
        // ๐Ÿ“ง Could send alerts to development team
        break;
        
      case 'high':
        console.error('๐Ÿ”ด HIGH PRIORITY:', error.message);
        break;
        
      case 'medium':
        console.warn('๐ŸŸก MEDIUM PRIORITY:', error.message);
        break;
        
      case 'low':
        console.info('๐ŸŸข LOW PRIORITY:', error.message);
        break;
    }
    
    // ๐ŸŽฏ Take appropriate action
    GameErrorHandler.handleErrorAction(error);
  }
  
  // ๐ŸŽช Different actions based on error type
  private static handleErrorAction(error: GameError): void {
    switch (error.code) {
      case GameErrorCode.CONNECTION_LOST:
        // ๐Ÿ”„ Attempt reconnection
        console.log('๐Ÿ”„ Attempting to reconnect player...');
        break;
        
      case GameErrorCode.GAME_FULL:
        // ๐Ÿ“‹ Add to waiting list
        console.log('๐Ÿ“‹ Adding player to waiting list...');
        break;
        
      case GameErrorCode.INVALID_MOVE:
        // โš ๏ธ Notify player
        console.log('โš ๏ธ Notifying player of invalid move...');
        break;
        
      default:
        console.log('๐Ÿค” Handling generic error...');
    }
  }
}

// ๐ŸŽฎ Example usage in game server
const processPlayerMove = (playerId: string, move: any): void => {
  try {
    // ๐ŸŽฏ Game logic here
    validateMove(move);
    applyMove(playerId, move);
  } catch (error) {
    const gameError: GameError = {
      name: 'InvalidMoveError',
      message: `Invalid move by player ${playerId} ๐ŸŽฎ`,
      code: GameErrorCode.INVALID_MOVE,
      playerId,
      severity: 'medium'
    };
    
    GameErrorHandler.handleGameError(gameError);
  }
};

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Error Recovery Strategies

When youโ€™re ready to level up, try this advanced pattern:

// ๐ŸŽฏ Advanced error recovery system
interface RecoveryStrategy {
  canRecover(error: Error): boolean;
  recover(error: Error, context: any): Promise<any>;
  fallback(error: Error, context: any): any;
}

class DatabaseRecoveryStrategy implements RecoveryStrategy {
  private retryAttempts = 3;
  private retryDelay = 1000; // ๐Ÿ• 1 second
  
  canRecover(error: Error): boolean {
    // ๐Ÿ” Check if it's a recoverable database error
    return error.message.includes('connection') || 
           error.message.includes('timeout');
  }
  
  async recover(error: Error, context: any): Promise<any> {
    console.log('๐Ÿ”„ Attempting database recovery...');
    
    for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
      try {
        // โณ Wait before retry
        await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
        
        // ๐Ÿ”„ Retry the operation
        return await context.operation();
      } catch (retryError) {
        console.log(`๐Ÿ’ฅ Retry attempt ${attempt} failed`);
        if (attempt === this.retryAttempts) {
          throw retryError;
        }
      }
    }
  }
  
  fallback(error: Error, context: any): any {
    // ๐Ÿ“‹ Return cached data or default response
    console.log('๐Ÿ“‹ Using fallback strategy...');
    return { success: false, message: 'Service temporarily unavailable ๐Ÿšง' };
  }
}

// ๐Ÿช„ Error recovery manager
class ErrorRecoveryManager {
  private strategies: RecoveryStrategy[] = [];
  
  addStrategy(strategy: RecoveryStrategy): void {
    this.strategies.push(strategy);
  }
  
  async handleErrorWithRecovery(error: Error, context: any): Promise<any> {
    // ๐Ÿ” Find a strategy that can handle this error
    const strategy = this.strategies.find(s => s.canRecover(error));
    
    if (strategy) {
      try {
        console.log('โœจ Attempting error recovery...');
        return await strategy.recover(error, context);
      } catch (recoveryError) {
        console.log('๐Ÿšซ Recovery failed, using fallback...');
        return strategy.fallback(error, context);
      }
    }
    
    // ๐Ÿ’ฅ No recovery strategy available
    throw error;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Error Monitoring Integration

For the brave developers:

// ๐Ÿš€ Integration with monitoring services
interface ErrorMonitor {
  reportError(error: Error, context: ErrorContext): void;
  reportMetric(metric: string, value: number): void;
}

interface ErrorContext {
  userId?: string;
  requestId?: string;
  environment: string;
  timestamp: Date;
  metadata?: Record<string, any>;
}

class ProductionErrorMonitor implements ErrorMonitor {
  reportError(error: Error, context: ErrorContext): void {
    // ๐Ÿ“Š Send to external monitoring service (like Sentry)
    const errorReport = {
      message: error.message,
      stack: error.stack,
      level: this.getErrorLevel(error),
      user: { id: context.userId },
      request: { id: context.requestId },
      environment: context.environment,
      timestamp: context.timestamp.toISOString(),
      extra: context.metadata
    };
    
    // ๐Ÿš€ This would integrate with your monitoring service
    console.log('๐Ÿ“Š Reporting to monitoring service:', errorReport);
  }
  
  reportMetric(metric: string, value: number): void {
    // ๐Ÿ“ˆ Track error metrics
    console.log(`๐Ÿ“ˆ Metric: ${metric} = ${value}`);
  }
  
  private getErrorLevel(error: Error): string {
    if (error.name.includes('Critical')) return 'error';
    if (error.name.includes('Warning')) return 'warning';
    return 'info';
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Swallowing Errors

// โŒ Wrong way - hiding errors!
const handleRequest = async (req: Request, res: Response) => {
  try {
    const data = await someAsyncOperation();
    res.json(data);
  } catch (error) {
    // ๐Ÿ’ฅ Silent failure - user never knows what happened!
    res.json({ success: false });
  }
};

// โœ… Correct way - proper error handling!
const handleRequest = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const data = await someAsyncOperation();
    res.json({ success: true, data });
  } catch (error) {
    // ๐ŸŽฏ Pass to global error handler
    next(error);
  }
};

๐Ÿคฏ Pitfall 2: Exposing Sensitive Information

// โŒ Dangerous - exposing internal details!
const errorHandler = (err: Error, req: Request, res: Response) => {
  res.status(500).json({
    error: err.message,
    stack: err.stack, // ๐Ÿ’ฅ Exposes internal structure!
    query: req.query  // ๐Ÿ’ฅ Might contain sensitive data!
  });
};

// โœ… Safe - sanitized error responses!
const errorHandler = (err: Error, req: Request, res: Response) => {
  const isProduction = process.env.NODE_ENV === 'production';
  
  res.status(500).json({
    success: false,
    message: isProduction ? 'Internal server error' : err.message,
    // ๐Ÿ›ก๏ธ Only include debug info in development
    ...((!isProduction) && { 
      stack: err.stack,
      timestamp: new Date().toISOString()
    })
  });
};

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Be Specific: Create custom error classes for different scenarios
  2. ๐Ÿ“ Log Everything: Include context like user ID, request ID, timestamp
  3. ๐Ÿ›ก๏ธ Sanitize Responses: Never expose sensitive data in error messages
  4. ๐ŸŽจ Use Error Codes: Implement consistent error codes for client handling
  5. โœจ Plan Recovery: Always have fallback strategies for critical operations
  6. ๐Ÿ“Š Monitor Metrics: Track error rates and patterns
  7. ๐Ÿ”„ Test Error Paths: Write tests for your error handling code

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Robust API Error System

Create a comprehensive error handling system for a social media API:

๐Ÿ“‹ Requirements:

  • โœ… Custom error classes for different scenarios (UserNotFound, PostNotFound, etc.)
  • ๐Ÿท๏ธ Error codes and severity levels
  • ๐Ÿ‘ค Context tracking (user ID, request ID)
  • ๐Ÿ“… Rate limiting error handling
  • ๐ŸŽจ Different responses for different client types (web vs mobile)
  • ๐Ÿ“Š Error metrics and monitoring

๐Ÿš€ Bonus Points:

  • Add error recovery for database failures
  • Implement circuit breaker pattern
  • Create error notification system
  • Add error correlation across microservices

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Our comprehensive error handling system!

// ๐Ÿ“‹ Base error interface
interface APIError extends Error {
  statusCode: number;
  errorCode: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
  isOperational: boolean;
  context?: Record<string, any>;
}

// ๐Ÿ—๏ธ Custom error classes
class UserNotFoundError extends Error implements APIError {
  public readonly statusCode = 404;
  public readonly errorCode = 'USER_NOT_FOUND';
  public readonly severity = 'medium' as const;
  public readonly isOperational = true;
  
  constructor(userId: string, context?: Record<string, any>) {
    super(`User with ID ${userId} not found ๐Ÿ‘ค`);
    this.name = 'UserNotFoundError';
    this.context = context;
  }
}

class RateLimitExceededError extends Error implements APIError {
  public readonly statusCode = 429;
  public readonly errorCode = 'RATE_LIMIT_EXCEEDED';
  public readonly severity = 'low' as const;
  public readonly isOperational = true;
  
  constructor(limit: number, resetTime: Date) {
    super(`Rate limit exceeded. Limit: ${limit}/hour. Resets at: ${resetTime.toISOString()} โฐ`);
    this.name = 'RateLimitExceededError';
  }
}

class DatabaseConnectionError extends Error implements APIError {
  public readonly statusCode = 503;
  public readonly errorCode = 'DATABASE_UNAVAILABLE';
  public readonly severity = 'critical' as const;
  public readonly isOperational = true;
  
  constructor(operation: string) {
    super(`Database connection failed during ${operation} ๐Ÿ’พ`);
    this.name = 'DatabaseConnectionError';
  }
}

// ๐ŸŽจ Error response formatter
class ErrorResponseFormatter {
  static formatForClient(error: APIError, clientType: 'web' | 'mobile', isDev: boolean) {
    const baseResponse = {
      success: false,
      error: {
        code: error.errorCode,
        message: this.getUserFriendlyMessage(error, clientType),
        timestamp: new Date().toISOString()
      }
    };
    
    // ๐Ÿ”ง Add debug info in development
    if (isDev) {
      return {
        ...baseResponse,
        debug: {
          originalMessage: error.message,
          stack: error.stack,
          context: error.context
        }
      };
    }
    
    return baseResponse;
  }
  
  private static getUserFriendlyMessage(error: APIError, clientType: 'web' | 'mobile'): string {
    const messages = {
      web: {
        USER_NOT_FOUND: 'The requested user could not be found. Please check the user ID and try again.',
        RATE_LIMIT_EXCEEDED: 'Too many requests. Please wait a moment before trying again.',
        DATABASE_UNAVAILABLE: 'Our service is temporarily unavailable. Please try again in a few minutes.'
      },
      mobile: {
        USER_NOT_FOUND: 'User not found ๐Ÿ‘ค',
        RATE_LIMIT_EXCEEDED: 'Too many requests โฐ',
        DATABASE_UNAVAILABLE: 'Service unavailable ๐Ÿšง'
      }
    };
    
    return messages[clientType][error.errorCode as keyof typeof messages.web] || 
           'An unexpected error occurred. Please try again.';
  }
}

// ๐Ÿ“Š Error metrics tracker
class ErrorMetrics {
  private static errorCounts = new Map<string, number>();
  private static lastReset = new Date();
  
  static trackError(error: APIError): void {
    const key = `${error.errorCode}_${error.severity}`;
    const current = this.errorCounts.get(key) || 0;
    this.errorCounts.set(key, current + 1);
    
    // ๐Ÿšจ Alert on critical errors
    if (error.severity === 'critical') {
      this.alertCriticalError(error);
    }
    
    // ๐Ÿ“ˆ Log metrics every hour
    this.maybeLogMetrics();
  }
  
  private static alertCriticalError(error: APIError): void {
    console.error('๐Ÿšจ CRITICAL ERROR ALERT:', {
      code: error.errorCode,
      message: error.message,
      context: error.context,
      timestamp: new Date().toISOString()
    });
    
    // ๐Ÿ“ง Here you would send alerts to your team
  }
  
  private static maybeLogMetrics(): void {
    const now = new Date();
    const hoursSinceReset = (now.getTime() - this.lastReset.getTime()) / (1000 * 60 * 60);
    
    if (hoursSinceReset >= 1) {
      console.log('๐Ÿ“Š Hourly Error Metrics:', Object.fromEntries(this.errorCounts));
      this.errorCounts.clear();
      this.lastReset = now;
    }
  }
}

// ๐Ÿ›ก๏ธ Main error handler
class SocialMediaErrorHandler {
  static handleError(
    err: APIError,
    req: Request,
    res: Response,
    next: NextFunction
  ): void {
    // ๐Ÿ“Š Track the error
    ErrorMetrics.trackError(err);
    
    // ๐Ÿ“ Log with context
    this.logError(err, req);
    
    // ๐ŸŽฏ Get client type from headers
    const clientType = req.headers['x-client-type'] === 'mobile' ? 'mobile' : 'web';
    const isDev = process.env.NODE_ENV === 'development';
    
    // ๐ŸŽจ Format response
    const response = ErrorResponseFormatter.formatForClient(err, clientType, isDev);
    
    // ๐Ÿ“ค Send response
    res.status(err.statusCode).json(response);
  }
  
  private static logError(err: APIError, req: Request): void {
    const logData = {
      timestamp: new Date().toISOString(),
      severity: err.severity,
      errorCode: err.errorCode,
      message: err.message,
      request: {
        method: req.method,
        url: req.url,
        userAgent: req.get('User-Agent'),
        ip: req.ip,
        userId: req.headers['x-user-id']
      },
      context: err.context
    };
    
    // ๐Ÿ“‹ Log based on severity
    switch (err.severity) {
      case 'critical':
        console.error('๐Ÿšจ CRITICAL:', JSON.stringify(logData, null, 2));
        break;
      case 'high':
        console.error('๐Ÿ”ด HIGH:', JSON.stringify(logData, null, 2));
        break;
      case 'medium':
        console.warn('๐ŸŸก MEDIUM:', JSON.stringify(logData, null, 2));
        break;
      case 'low':
        console.info('๐ŸŸข LOW:', JSON.stringify(logData, null, 2));
        break;
    }
  }
}

// ๐ŸŽฎ Example usage
app.get('/users/:id', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const user = await findUserById(req.params.id);
    if (!user) {
      throw new UserNotFoundError(req.params.id, { 
        requestId: req.headers['x-request-id'],
        userAgent: req.get('User-Agent')
      });
    }
    
    res.json({ success: true, data: user });
  } catch (error) {
    next(error);
  }
});

// ๐Ÿ›ก๏ธ Register the error handler
app.use(SocialMediaErrorHandler.handleError);

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create global error handlers with confidence ๐Ÿ’ช
  • โœ… Avoid common mistakes that crash applications ๐Ÿ›ก๏ธ
  • โœ… Apply best practices in real projects ๐ŸŽฏ
  • โœ… Debug issues like a pro ๐Ÿ›
  • โœ… Build resilient backends with TypeScript! ๐Ÿš€

Remember: Error handling isnโ€™t just about catching errors - itโ€™s about creating great user experiences even when things go wrong! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered global error handling in TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the exercises above
  2. ๐Ÿ—๏ธ Build a project with comprehensive error handling
  3. ๐Ÿ“š Move on to our next tutorial: โ€œLogging: Structured Logging with Winstonโ€
  4. ๐ŸŒŸ Share your error handling strategies with others!

Remember: Every backend expert was once a beginner. Keep coding, keep learning, and most importantly, handle those errors gracefully! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ