Effective TypeScript Error Handling Patterns for Scalable Web Applications

Learn beginner-friendly TypeScript error handling techniques to build scalable and maintainable web applications.

Error handling is a crucial part of building robust web applications. With TypeScript, you can catch many errors during development, but you still need good runtime error handling to manage unexpected issues gracefully. In this article, we'll explore effective and beginner-friendly patterns for handling errors in TypeScript, helping you write code that is easier to maintain and scale.

### Why is Error Handling Important in TypeScript?

Even with TypeScript's static typing, some errors can only be caught at runtime, such as network failures or invalid API responses. Handling these errors properly prevents your application from crashing and improves user experience by showing meaningful messages or fallback UI.

### 1. Using `try...catch` Blocks

`try...catch` is the simplest and most common way to handle errors in synchronous and asynchronous functions. You wrap code that might fail in a `try` block, then handle the error in the `catch` block.

typescript
function parseJSON(jsonString: string): object | null {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.error('Invalid JSON string:', error);
    return null;
  }
}

In this example, invalid JSON strings are caught without breaking the application.

### 2. Creating Custom Error Classes

For larger apps, define custom error classes to provide more context about errors. This helps with better error identification and handling.

typescript
class AppError extends Error {
  public readonly isOperational: boolean;

  constructor(message: string, isOperational = true) {
    super(message);
    this.isOperational = isOperational;
    Object.setPrototypeOf(this, new.target.prototype); // Restore prototype chain
    this.name = this.constructor.name;
  }
}

// Usage
function fetchData(url: string): Promise<string> {
  return fetch(url).then(response => {
    if (!response.ok) {
      throw new AppError('Failed to fetch data');
    }
    return response.text();
  });
}

Custom errors give you flexibility to distinguish between types of errors for better handling strategies.

### 3. Handling Async Errors with `async/await`

In asynchronous code, use `try...catch` with `async/await` to handle errors clearly and avoid nested callbacks.

typescript
async function getUserData(userId: string) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error('User not found');
    }
    return await response.json();
  } catch (error) {
    console.error('Error fetching user data:', error);
    return null;
  }
}

This pattern keeps async error handling easy to read and maintain.

### 4. Using Result Types Instead of Throwing

Another pattern to avoid exceptions is using a "result" type that returns success or error objects. This approach improves explicit handling of success and failure states.

typescript
type Result<T> = {
  success: true;
  data: T;
} | {
  success: false;
  error: string;
};

function divide(a: number, b: number): Result<number> {
  if (b === 0) {
    return { success: false, error: 'Division by zero' };
  }
  return { success: true, data: a / b };
}

const result = divide(10, 0);
if (!result.success) {
  console.error('Error:', result.error);
} else {
  console.log('Result:', result.data);
}

This method helps avoid unexpected runtime errors and forces you to check for error conditions.

### 5. Logging and Monitoring

Finally, robust error handling includes logging errors and monitoring them in production. Tools like Sentry or LogRocket help catch errors users experience and give insights for fixes.

### Summary

Effective error handling in TypeScript combines static typing with good runtime patterns. Use `try...catch` for synchronous and async code, create custom error classes for clarity, consider result types to handle success and failure explicitly, and always log errors for real-time monitoring. Applying these beginner-friendly patterns will make your web applications scalable and easier to maintain.