Error Handling
Principles
- Fail fast - Detect errors early
- Fail loud - Log clearly
- Fail gracefully - Clean UX
- Recover when possible - Retry, fallback
Custom errors
// Base error
class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500
) {
super(message);
this.name = this.constructor.name;
}
}
// Specific errors
class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(`${resource} not found: ${id}`, 'NOT_FOUND', 404);
}
}
class ValidationError extends AppError {
constructor(public errors: Record<string, string>) {
super('Validation failed', 'VALIDATION_ERROR', 400);
}
}
API Error Response
// Error handler middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
}
});
}
// Log unexpected errors
logger.error({ err, requestId: req.id }, 'Unexpected error');
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'Something went wrong',
}
});
});
React Error Boundary
class ErrorBoundary extends Component<Props, State> {
state = { hasError: false, error: null };
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
logger.error({ error, info }, 'React error boundary');
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
Retry Pattern
async function withRetry<T>(
fn: () => Promise<T>,
retries = 3,
delay = 1000
): Promise<T> {
try {
return await fn();
} catch (error) {
if (retries === 0) throw error;
await sleep(delay);
return withRetry(fn, retries - 1, delay * 2);
}
}