Mastering TypeScript Error Handling Patterns for Scalable System Design
Learn practical TypeScript error handling techniques to build scalable and maintainable applications with clear, beginner-friendly patterns.
Error handling is a crucial aspect of building scalable and maintainable applications. TypeScript, with its strong typing system, provides excellent tools to manage errors effectively and ensure your code is robust and easy to debug. This article introduces beginner-friendly error handling patterns in TypeScript to help you design systems that scale well.
### Why Handle Errors Properly?
Proper error handling prevents your application from crashing unexpectedly and helps you provide meaningful feedback to users or developers. It also makes debugging easier and maintains a clean separation between business logic and error management.
### Basic Try-Catch Pattern
The simplest way to catch errors in TypeScript is using the `try-catch` statement. Use it around code that might throw an exception.
function parseJSON(jsonString: string): unknown {
try {
return JSON.parse(jsonString);
} catch (error) {
// Handle or log the error here
console.error('Parsing error:', error);
return null;
}
}
const result = parseJSON('{ invalid json }');
console.log(result); // null### Using Error Classes for Custom Errors
Creating custom error classes helps you define specific error types and makes error handling more expressive and organized.
class NotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = 'NotFoundError';
}
}
function getUser(id: number) {
// Simulate user lookup
if (id !== 1) {
throw new NotFoundError(`User with id ${id} not found.`);
}
return { id: 1, name: 'Alice' };
}
try {
const user = getUser(2);
console.log(user);
} catch (error) {
if (error instanceof NotFoundError) {
console.warn(error.message);
} else {
console.error('Unexpected error', error);
}
}### Using Union Types for Return Error Handling
For functions that might fail without throwing exceptions, consider returning a union type representing success or error results. This approach avoids exceptions and makes error states explicit.
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.log('Result:', result.data);
} else {
console.error('Error:', result.error);
}### Async/Await Error Handling
When working with asynchronous code, use `try-catch` with `async-await` to handle errors gracefully.
async function fetchData(url: string): Promise<void> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
const data = await response.json();
console.log('Fetched data:', data);
} catch (error) {
if (error instanceof Error) {
console.error('Fetch error:', error.message);
} else {
console.error('Unknown error');
}
}
}
fetchData('https://api.example.com/data');### Summary
To build scalable systems with TypeScript, practice clear error handling patterns such as using try-catch blocks, defining custom error classes, leveraging union types for result handling, and handling async errors appropriately. These techniques improve code reliability, readability, and maintainability, making your applications easier to debug and extend.