Skip to content

Error Handling

Understanding and handling API errors effectively.

Error Response Format

All API errors follow a consistent format:

json
{
  "success": false,
  "error": "Error message description",
  "code": "ERROR_CODE",
  "details": {
    "field": "Additional context"
  }
}

HTTP Status Codes

2xx Success

CodeStatusDescription
200OKRequest succeeded
201CreatedResource created successfully
204No ContentRequest succeeded, no content returned

4xx Client Errors

CodeStatusDescription
400Bad RequestInvalid request parameters or body
401UnauthorizedMissing or invalid authentication token
403ForbiddenInsufficient permissions for this action
404Not FoundResource not found
405Method Not AllowedHTTP method not supported for this endpoint
409ConflictResource conflict (e.g., duplicate entry)
422Unprocessable EntityRequest valid but unable to process
429Too Many RequestsRate limit exceeded

5xx Server Errors

CodeStatusDescription
500Internal Server ErrorUnexpected server error
502Bad GatewayServer received invalid response
503Service UnavailableService temporarily unavailable
504Gateway TimeoutServer timeout

Common Error Codes

Authentication Errors

json
{
  "success": false,
  "error": "Invalid or expired token",
  "code": "AUTH_TOKEN_INVALID"
}

Causes:

  • Token expired
  • Invalid token signature
  • Token revoked

Solution: Re-authenticate with /api/auth/login


Authorization Errors

json
{
  "success": false,
  "error": "Admin privileges required",
  "code": "AUTH_INSUFFICIENT_PERMISSIONS"
}

Causes:

  • User not admin
  • Attempting admin-only operation

Solution: Contact server administrator


Validation Errors

json
{
  "success": false,
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "username": "Username is required",
    "password": "Password must be at least 6 characters"
  }
}

Causes:

  • Missing required fields
  • Invalid field format
  • Field constraints not met

Solution: Correct input and retry


Resource Not Found

json
{
  "success": false,
  "error": "Player not found",
  "code": "PLAYER_NOT_FOUND"
}

Causes:

  • Player offline
  • Invalid username
  • Player doesn't exist

Solution: Verify player exists and is online


Rate Limiting

json
{
  "success": false,
  "error": "Too many requests",
  "code": "RATE_LIMIT_EXCEEDED",
  "details": {
    "retryAfter": 60
  }
}

Causes:

  • Too many requests in short time

Solution: Wait and retry after retryAfter seconds

Error Handling Examples

TypeScript

typescript
class APIError extends Error {
  constructor(
    public statusCode: number,
    public code: string,
    public details?: any
  ) {
    super(`API Error ${statusCode}: ${code}`);
  }
}

async function handleAPICall<T>(
  request: () => Promise<Response>
): Promise<T> {
  try {
    const response = await request();
    const data = await response.json();
    
    if (!response.ok) {
      throw new APIError(
        response.status,
        data.code || 'UNKNOWN_ERROR',
        data.details
      );
    }
    
    return data;
  } catch (error) {
    if (error instanceof APIError) {
      // Handle specific error codes
      switch (error.code) {
        case 'AUTH_TOKEN_INVALID':
          // Redirect to login
          window.location.href = '/login';
          break;
        case 'RATE_LIMIT_EXCEEDED':
          // Wait and retry
          await new Promise(resolve => 
            setTimeout(resolve, error.details?.retryAfter * 1000 || 60000)
          );
          return handleAPICall(request);
        default:
          console.error('API Error:', error);
      }
    }
    throw error;
  }
}

// Usage
try {
  const data = await handleAPICall(() =>
    fetch('http://localhost:8080/api/players', {
      headers: { 'Authorization': `Bearer ${token}` }
    })
  );
  console.log('Players:', data);
} catch (error) {
  console.error('Failed to fetch players:', error);
}

Python

python
class APIError(Exception):
    def __init__(self, status_code, code, details=None):
        self.status_code = status_code
        self.code = code
        self.details = details
        super().__init__(f"API Error {status_code}: {code}")

def handle_api_call(request_func):
    """Handle API calls with error handling"""
    try:
        response = request_func()
        
        if not response.ok:
            data = response.json()
            raise APIError(
                response.status_code,
                data.get('code', 'UNKNOWN_ERROR'),
                data.get('details')
            )
        
        return response.json()
    
    except APIError as e:
        if e.code == 'AUTH_TOKEN_INVALID':
            # Re-authenticate
            print("Token expired, please login again")
        elif e.code == 'RATE_LIMIT_EXCEEDED':
            # Wait and retry
            retry_after = e.details.get('retryAfter', 60)
            print(f"Rate limited, waiting {retry_after}s")
            time.sleep(retry_after)
            return handle_api_call(request_func)
        else:
            print(f"API Error: {e}")
        raise

# Usage
try:
    data = handle_api_call(lambda: requests.get(
        'http://localhost:8080/api/players',
        headers={'Authorization': f'Bearer {token}'}
    ))
    print('Players:', data)
except APIError as e:
    print(f"Failed to fetch players: {e}")

Retry Logic

typescript
async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // Exponential backoff
      const delay = baseDelay * Math.pow(2, i);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

// Usage
const data = await retryWithBackoff(
  () => fetchPlayers(token),
  3, // max 3 retries
  1000 // 1 second base delay
);

Best Practices

1. Always Check Response Status

typescript
const response = await fetch(url);
if (!response.ok) {
  const error = await response.json();
  throw new Error(error.message);
}

2. Handle Token Expiration

typescript
if (error.code === 'AUTH_TOKEN_INVALID') {
  // Refresh token or redirect to login
  await refreshToken();
  // Retry original request
}

3. Implement Exponential Backoff

For transient errors (5xx), use exponential backoff:

  • 1st retry: wait 1s
  • 2nd retry: wait 2s
  • 3rd retry: wait 4s

4. Log Errors Appropriately

typescript
console.error('API Error:', {
  endpoint: url,
  status: error.statusCode,
  code: error.code,
  details: error.details,
  timestamp: new Date().toISOString()
});

5. User-Friendly Messages

typescript
function getUserMessage(errorCode: string): string {
  const messages = {
    'AUTH_TOKEN_INVALID': 'Your session has expired. Please login again.',
    'AUTH_INSUFFICIENT_PERMISSIONS': 'You do not have permission for this action.',
    'PLAYER_NOT_FOUND': 'Player not found. Please check the username.',
    'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment.',
  };
  return messages[errorCode] || 'An unexpected error occurred. Please try again.';
}

Debugging Tips

Enable Debug Logging

typescript
const DEBUG = process.env.NODE_ENV === 'development';

if (DEBUG) {
  console.log('API Request:', {
    url,
    method,
    headers,
    body
  });
}

Network Tab

Use browser DevTools Network tab to inspect:

  • Request/response headers
  • Response body
  • Timing information
  • HTTP status codes

API Testing Tools

  • Postman: Test endpoints interactively
  • Insomnia: REST client with GraphQL support
  • cURL: Command-line testing
  • HTTPie: User-friendly HTTP client

Released under the MIT License.