Back to Documentation
Error Handling Best Practices
Learn how to implement robust error handling for production-ready currency applications.
Common Error Scenarios
API Errors
- • Invalid API key (401)
- • Rate limiting (429)
- • Server errors (500)
- • Network failures
Validation Errors
- • Invalid currency codes
- • Negative amounts
- • Missing parameters
- • Malformed requests
Error Handling Implementation
// Comprehensive error handling wrapper class CurrencyAPIError extends Error { constructor(message, statusCode, originalError) { super(message) this.name = 'CurrencyAPIError' this.statusCode = statusCode this.originalError = originalError } } async function callCurrencyAPI(endpoint, options = {}) { try { const response = await fetch(endpoint, { ...options, headers: { 'X-API-Key': process.env.API_KEY, 'Content-Type': 'application/json', ...options.headers } }) // Handle different status codes switch (response.status) { case 200: return await response.json() case 400: const badRequestData = await response.json() throw new CurrencyAPIError( badRequestData.detail || 'Invalid request parameters', 400, badRequestData ) case 401: throw new CurrencyAPIError( 'Invalid API key. Please check your authentication.', 401 ) case 429: const retryAfter = response.headers.get('Retry-After') || '60' throw new CurrencyAPIError( `Rate limit exceeded. Try again in ${retryAfter} seconds.`, 429, { retryAfter: parseInt(retryAfter) } ) case 500: case 502: case 503: throw new CurrencyAPIError( 'Server error. Please try again later.', response.status ) default: throw new CurrencyAPIError( `Unexpected error: ${response.status}`, response.status ) } } catch (error) { // Handle network errors if (error instanceof TypeError) { throw new CurrencyAPIError( 'Network error. Please check your connection.', 0, error ) } // Re-throw our custom errors if (error instanceof CurrencyAPIError) { throw error } // Handle unexpected errors throw new CurrencyAPIError( 'An unexpected error occurred.', 0, error ) } }
User-Friendly Error Messages
// Error message mapping for better UX function getErrorMessage(error) { if (!(error instanceof CurrencyAPIError)) { return 'Something went wrong. Please try again.' } switch (error.statusCode) { case 400: return 'Please check your input and try again.' case 401: return 'Authentication failed. Please check your API key.' case 429: const retrySeconds = error.originalError?.retryAfter || 60 return `Too many requests. Please wait ${retrySeconds} seconds before trying again.` case 500: case 502: case 503: return 'Our servers are experiencing issues. Please try again in a few minutes.' case 0: // Network error return 'Please check your internet connection and try again.' default: return 'An unexpected error occurred. Please try again later.' } } // React component with error handling function CurrencyConverter() { const [error, setError] = useState(null) const [loading, setLoading] = useState(false) const handleConversion = async (from, to, amount) => { setLoading(true) setError(null) try { const result = await callCurrencyAPI( `/convert?from=${from}&to=${to}&amount=${amount}` ) // Handle success... } catch (err) { setError(getErrorMessage(err)) // Log detailed error for debugging console.error('Currency conversion failed:', err) // Optional: Send to error tracking service // errorTracker.captureException(err) } finally { setLoading(false) } } return ( <div> {/* Your form here */} {error && ( <div className="bg-destructive/10 border border-destructive/30 rounded-lg p-4 mt-4"> <div className="flex items-center"> <AlertTriangle className="h-5 w-5 text-destructive mr-2" /> <span className="text-destructive-foreground">{error}</span> </div> </div> )} </div> ) }
Retry Logic with Exponential Backoff
async function withRetry(apiCall, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await apiCall() } catch (error) { // Don't retry on authentication or validation errors if (error.statusCode === 401 || error.statusCode === 400) { throw error } // Don't retry on the last attempt if (attempt === maxRetries) { throw error } // Calculate delay with exponential backoff const delay = Math.pow(2, attempt) * 1000 // 2s, 4s, 8s... console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`) await new Promise(resolve => setTimeout(resolve, delay)) } } } // Usage const convertCurrency = async (from, to, amount) => { return withRetry(() => callCurrencyAPI(`/convert?from=${from}&to=${to}&amount=${amount}`) ) }
Error Monitoring & Logging
Production Monitoring
- Track error rates and types
- Monitor API response times
- Set up alerts for critical failures
- Log structured error data
Testing Error Scenarios
// Jest test for error handling describe('Currency API Error Handling', () => { test('handles rate limiting gracefully', async () => { fetch.mockResolvedValueOnce({ status: 429, headers: new Map([['Retry-After', '30']]), json: async () => ({ detail: 'Rate limit exceeded' }) }) await expect( callCurrencyAPI('/convert?from=USD&to=EUR&amount=100') ).rejects.toThrow('Rate limit exceeded') }) test('handles network errors', async () => { fetch.mockRejectedValueOnce(new TypeError('Network error')) await expect( callCurrencyAPI('/convert?from=USD&to=EUR&amount=100') ).rejects.toThrow('Network error') }) })
Build Resilient Apps
Proper error handling is crucial for production applications