veza/docs/archive/root-md/RATE_LIMITING_COMMUNICATION_GUIDE.md
senke 43af35fd93 chore(audit 2.2, 2.3): nettoyer .md et .json à la racine
- Archiver 131 .md dans docs/archive/root-md/
- Archiver 22 .json dans docs/archive/root-json/
- Conserver 7 .md utiles (README, CONTRIBUTING, CHANGELOG, etc.)
- Conserver package.json, package-lock.json, turbo.json
- Ajouter README d'index dans chaque archive
2026-02-15 14:35:08 +01:00

10 KiB

Rate Limiting Communication Guide

INT-013: Add API rate limiting communication

Date: 2025-12-25
Status: Completed

Overview

This guide documents how rate limiting is communicated between the Veza backend API and frontend clients. It covers response formats, headers, error handling, and best practices.

Backend Rate Limiting

Rate Limit Configuration

The Veza API implements multiple rate limiting strategies:

  1. IP-based rate limiting (unauthenticated users)

    • Default: 100 requests per minute
    • Burst: 10 requests
  2. User-based rate limiting (authenticated users)

    • Default: 1000 requests per minute
    • Burst: 100 requests
  3. Endpoint-specific rate limiting

    • Login attempts: Configurable (default: 5 attempts per 15 minutes)
    • Upload endpoints: 10 uploads per hour per user

Rate Limit Response Format

When a rate limit is exceeded, the backend returns:

HTTP Status: 429 Too Many Requests

Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1703509200
Retry-After: 60

Response Body (Standardized APIResponse format):

{
  "success": false,
  "error": {
    "code": 429,
    "message": "Rate limit exceeded. Please try again later.",
    "details": [
      {
        "field": "rate_limit",
        "message": "You have exceeded the rate limit of 100 requests per minute"
      }
    ],
    "retry_after": 60,
    "limit": 100,
    "remaining": 0,
    "reset": 1703509200
  }
}

Response Fields

  • code: Error code (429 for rate limiting)
  • message: Human-readable error message
  • details: Array of validation/error details
  • retry_after: Number of seconds to wait before retrying
  • limit: Maximum number of requests allowed
  • remaining: Number of requests remaining (0 when limit exceeded)
  • reset: Unix timestamp when the rate limit resets

Headers Explained

  • X-RateLimit-Limit: Maximum number of requests allowed in the time window
  • X-RateLimit-Remaining: Number of requests remaining in the current window
  • X-RateLimit-Reset: Unix timestamp when the rate limit window resets
  • Retry-After: Number of seconds to wait before retrying (RFC 7231)

Frontend Rate Limit Handling

Error Detection

The frontend detects rate limit errors by:

  1. HTTP Status Code: 429
  2. Response Format: Standardized APIResponse with success: false
  3. Error Code: 429 in error object

Error Parsing

The frontend parses rate limit errors in apiErrorHandler.ts:

if (status === 429) {
  // Extract rate limit information from headers
  const rateLimitLimit = headers['x-ratelimit-limit'];
  const rateLimitRemaining = headers['x-ratelimit-remaining'];
  const rateLimitReset = headers['x-ratelimit-reset'];
  const retryAfter = headers['retry-after'] || data?.error?.retry_after || 60;
  
  // Calculate time until reset
  const resetTime = rateLimitReset 
    ? new Date(rateLimitReset * 1000) 
    : undefined;
  const secondsUntilReset = resetTime 
    ? Math.max(0, Math.ceil((resetTime.getTime() - Date.now()) / 1000))
    : retryAfter;
  
  return {
    code: 429,
    message: data?.error?.message || 'Trop de requêtes. Veuillez patienter avant de réessayer.',
    retry_after: secondsUntilReset,
    rate_limit: {
      limit: rateLimitLimit,
      remaining: rateLimitRemaining,
      reset: resetTime?.toISOString(),
    },
  };
}

User Notification

When a rate limit error occurs:

  1. Toast Notification: Shows error message with retry information

    toast.error(errorMessage, {
      duration: 8000, // Longer duration for rate limit errors
    });
    
  2. Error Message: User-friendly message in French

    "Trop de requêtes. Veuillez patienter quelques instants"
    
  3. Retry Information: Includes time until reset

    "Réessayez dans 60 secondes"
    

Automatic Retry

The frontend implements automatic retry for rate limit errors:

  1. Retry Configuration: Rate limit errors are included in retryable status codes

    retryableStatusCodes: [429, 500, 502, 503, 504]
    
  2. Exponential Backoff: Retries use exponential backoff

    // For 429 errors, use retry_after if available
    if (status === 429 && retryAfter) {
      delay = retryAfter * 1000; // Use retry_after in seconds
    }
    
  3. Retry Limits: Maximum retries configured to prevent infinite loops

    maxRetries: 3
    

Best Practices

For Backend Developers

  1. Always Include Headers: Include all rate limit headers in responses

    c.Header("X-RateLimit-Limit", strconv.Itoa(limit))
    c.Header("X-RateLimit-Remaining", strconv.Itoa(remaining))
    c.Header("X-RateLimit-Reset", strconv.FormatInt(resetTime, 10))
    c.Header("Retry-After", strconv.Itoa(retryAfter))
    
  2. Use Standardized Format: Use APIResponse format for consistency

    c.JSON(http.StatusTooManyRequests, gin.H{
      "success": false,
      "error": gin.H{
        "code":    429,
        "message": "Rate limit exceeded. Please try again later.",
        "retry_after": retryAfter,
        "limit": limit,
        "remaining": 0,
        "reset": resetTime,
      },
    })
    
  3. Calculate Accurate Reset Time: Use actual window reset time, not fixed values

    resetTime := time.Now().Add(window).Unix()
    
  4. Provide Clear Messages: Include helpful error messages

    "message": fmt.Sprintf("You have exceeded the rate limit of %d requests per %v", limit, window)
    

For Frontend Developers

  1. Check Headers First: Always check rate limit headers before parsing body

    const rateLimitLimit = headers['x-ratelimit-limit'];
    const rateLimitRemaining = headers['x-ratelimit-remaining'];
    
  2. Use Retry-After: Respect the Retry-After header for retry timing

    const retryAfter = headers['retry-after'] || 60;
    
  3. Show User-Friendly Messages: Display clear messages to users

    "Trop de requêtes. Veuillez patienter quelques instants"
    
  4. Implement Exponential Backoff: Use exponential backoff for retries

    delay = Math.min(delay * 2, maxDelay);
    
  5. Track Rate Limit State: Store rate limit information for UI display

    rate_limit: {
      limit: rateLimitLimit,
      remaining: rateLimitRemaining,
      reset: resetTime,
    }
    

Rate Limit Headers Usage

Reading Headers in Frontend

// Get rate limit information from response headers
const getRateLimitInfo = (response: AxiosResponse) => {
  const headers = response.headers;
  return {
    limit: parseInt(headers['x-ratelimit-limit'] || '0', 10),
    remaining: parseInt(headers['x-ratelimit-remaining'] || '0', 10),
    reset: parseInt(headers['x-ratelimit-reset'] || '0', 10),
    retryAfter: parseInt(headers['retry-after'] || '0', 10),
  };
};

Displaying Rate Limit Status

// Show rate limit status in UI
const RateLimitIndicator = ({ rateLimit }) => {
  if (!rateLimit) return null;
  
  const resetTime = new Date(rateLimit.reset * 1000);
  const timeUntilReset = Math.ceil((resetTime.getTime() - Date.now()) / 1000);
  
  return (
    <div className="rate-limit-indicator">
      <span>Requests: {rateLimit.remaining} / {rateLimit.limit}</span>
      {rateLimit.remaining === 0 && (
        <span>Resets in {timeUntilReset} seconds</span>
      )}
    </div>
  );
};

Testing Rate Limiting

Backend Tests

func TestRateLimitMiddleware(t *testing.T) {
    router := setupTestRouter()
    
    // Make requests up to limit
    for i := 0; i < 100; i++ {
        w := httptest.NewRecorder()
        req := httptest.NewRequest("GET", "/api/v1/tracks", nil)
        router.ServeHTTP(w, req)
        
        if i < 100 {
            assert.Equal(t, http.StatusOK, w.Code)
        } else {
            assert.Equal(t, http.StatusTooManyRequests, w.Code)
            assert.Equal(t, "0", w.Header().Get("X-RateLimit-Remaining"))
        }
    }
}

Frontend Tests

describe('Rate Limit Handling', () => {
  it('should parse rate limit error correctly', () => {
    const error = {
      response: {
        status: 429,
        headers: {
          'x-ratelimit-limit': '100',
          'x-ratelimit-remaining': '0',
          'x-ratelimit-reset': '1703509200',
          'retry-after': '60',
        },
        data: {
          success: false,
          error: {
            code: 429,
            message: 'Rate limit exceeded',
            retry_after: 60,
          },
        },
      },
    };
    
    const apiError = parseApiError(error);
    expect(apiError.code).toBe(429);
    expect(apiError.retry_after).toBe(60);
    expect(apiError.rate_limit.limit).toBe(100);
  });
});

Common Scenarios

Scenario 1: User Exceeds Rate Limit

  1. User makes 101 requests in 1 minute (limit: 100)
  2. Backend returns 429 with rate limit headers
  3. Frontend shows toast: "Trop de requêtes. Veuillez patienter quelques instants"
  4. Frontend waits retry_after seconds before retrying
  5. Request automatically retries after delay

Scenario 2: Rate Limit Reset

  1. User hits rate limit at 10:00:00
  2. Backend sets X-RateLimit-Reset: 1703509260 (10:01:00)
  3. Frontend calculates: 60 seconds until reset
  4. User sees: "Réessayez dans 60 secondes"
  5. At 10:01:00, rate limit resets automatically

Scenario 3: Different Limits for Authenticated Users

  1. Unauthenticated user: 100 requests/minute
  2. Authenticated user: 1000 requests/minute
  3. Headers reflect appropriate limit
  4. Frontend displays limit based on user status

Troubleshooting

Issue: Rate limit not working

Check:

  • Rate limit middleware is applied
  • Redis is running (if using Redis-based rate limiting)
  • Headers are being set correctly

Issue: Frontend not showing rate limit errors

Check:

  • Error handler is checking for status 429
  • Headers are being parsed correctly
  • Toast notifications are enabled

Issue: Retry not respecting retry_after

Check:

  • retry_after is being extracted from headers
  • Retry delay is using retry_after value
  • Exponential backoff is not overriding retry_after

References


Last Updated: 2025-12-25
Maintained By: Veza Backend Team