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