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