Error Handling Best Practices

Patterns and strategies for handling Geog API errors gracefully in your applications.

Client Implementation

Retry Wrapper with Error Handling

// Helper function for delays
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Token management (implement based on your auth system)
let token: string;
async function refreshToken() {
  /* ... */
}

async function apiRequest(url: string, options?: RequestInit, retryCount = 0) {
  const MAX_RETRIES = 3;

  const response = await fetch(url, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...options?.headers,
    },
  });

  if (!response.ok) {
    const error = await response.json();

    switch (error.code) {
      case "TOKEN_EXPIRED":
        // Refresh token and retry (with retry limit)
        if (retryCount < MAX_RETRIES) {
          await refreshToken();
          return apiRequest(url, options, retryCount + 1);
        }
        throw new ApiError(error);

      case "RATE_LIMIT_EXCEEDED":
        // Wait and retry with backoff (with retry limit)
        if (retryCount < MAX_RETRIES) {
          await sleep(error.retryAfter * 1000);
          return apiRequest(url, options, retryCount + 1);
        }
        throw new ApiError(error);

      case "VALIDATION_ERROR":
        // Handle validation errors
        throw new ValidationError(error.details);

      default:
        throw new ApiError(error);
    }
  }

  return response.json();
}

Logging Errors

Include the error code and message in logs for debugging:

try {
  await apiRequest("/v1/tiles/tileset/14/4680/6125.mvt");
} catch (error) {
  const { code, message } = error as ApiError;
  console.error(`API Error [${code}]: ${message}`);
}

User-Facing Messages

Map error codes to user-friendly messages:

const userMessages = {
  AUTH_REQUIRED: "Please log in to continue.",
  TOKEN_EXPIRED: "Your session has expired. Please log in again.",
  INSUFFICIENT_SCOPE: "You do not have permission to access this resource.",
  RATE_LIMIT_EXCEEDED: "Too many requests. Please wait a moment.",
};

Handling Rate Limits

Free Tier Limit Handling

async function fetchTile(url: string, token: string) {
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (response.status === 429) {
    const data = await response.json();
    const resetDate = new Date(data.reset);

    // Show user when limits reset
    console.warn(
      `Rate limit exceeded. Resets at ${resetDate.toLocaleString()}`,
    );

    // Consider upgrading
    showUpgradePrompt();
    return null;
  }

  return response;
}

Tips for free tier limits:

  1. Monitor rate limit headers: Check X-RateLimit-<Type>-Remaining before making requests
  2. Cache aggressively: Free tier limits are generous but caching reduces requests
  3. Query usage endpoint: Monitor your usage throughout the day to avoid surprises

Exponential Backoff

// Helper function for delays
const sleep = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms));

async function fetchWithRetry(url: string, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const response = await fetch(url);
    if (response.status === 429) {
      const retryAfter = parseInt(
        response.headers.get("Retry-After") || "60",
        10,
      );
      await sleep(retryAfter * 1000 * Math.pow(2, i));
      continue;
    }
    return response;
  }
  throw new Error("Rate limit exceeded after retries");
}

Optimizing Usage

  1. Cache responses: Vector tiles are cacheable for 24 hours. Implement client-side caching.
  2. Use appropriate zoom levels: Request only the zoom levels needed for your use case.
  3. Implement request deduplication: Avoid duplicate requests for the same resource.

See Also