Skip to content

Latest commit

 

History

History
666 lines (517 loc) · 16.2 KB

EXAMPLES.md

File metadata and controls

666 lines (517 loc) · 16.2 KB

SDK Usage Examples

Complete examples demonstrating how to use the Geo API SDK.

Table of Contents


Basic Setup

Node.js / Express Application

import { ApogeoAPI } from 'apogeoapi';
import express from 'express';

const app = express();
const client = new ApogeoAPI({
  apiKey: process.env.GEO_API_KEY!,
  baseURL: process.env.GEO_API_URL || 'https://api.apogeoapi.com/v1',
  timeout: 30000,
  retries: 3
});

app.get('/countries', async (req, res) => {
  try {
    const countries = await client.geo.getCountries();
    res.json(countries);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);

Next.js API Route

// pages/api/countries.ts
import { ApogeoAPI } from 'apogeoapi';
import type { NextApiRequest, NextApiResponse } from 'next';

const client = new ApogeoAPI({
  apiKey: process.env.GEO_API_KEY!
});

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    const countries = await client.geo.getCountries();
    res.status(200).json(countries);
  } catch (error: any) {
    res.status(error.statusCode || 500).json({
      error: error.message
    });
  }
}

React Hook

// hooks/useGeoAPI.ts
import { ApogeoAPI } from 'apogeoapi';
import { useState, useEffect } from 'react';

const client = new ApogeoAPI({
  apiKey: process.env.NEXT_PUBLIC_GEO_API_KEY!
});

export function useCountries() {
  const [countries, setCountries] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    client.geo.getCountries()
      .then(setCountries)
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);

  return { countries, loading, error };
}

// Usage in component
function CountriesList() {
  const { countries, loading, error } = useCountries();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {countries.map(country => (
        <li key={country.id}>{country.name}</li>
      ))}
    </ul>
  );
}

Authentication Flow

Complete Registration & Login Flow

import { ApogeoAPI, ApogeoAPIError } from 'apogeoapi';

// Initialize without auth (for registration)
const publicClient = new ApogeoAPI({
  apiKey: 'public_api_key' // Or omit for public endpoints
});

async function registerAndLogin() {
  try {
    // 1. Register
    const registration = await publicClient.auth.register({
      email: 'user@example.com',
      password: 'SecurePassword123!',
      username: 'johndoe'
    });

    console.log('User registered:', registration.user);
    console.log('Access token:', registration.access_token);

    // 2. Create new client with token
    const authenticatedClient = new ApogeoAPI({
      token: registration.access_token
    });

    // 3. Verify profile
    const profile = await authenticatedClient.auth.getProfile();
    console.log('Profile:', profile);

    // 4. Store tokens securely
    localStorage.setItem('access_token', registration.access_token);
    localStorage.setItem('refresh_token', registration.refresh_token);

    return authenticatedClient;
  } catch (error) {
    if (error instanceof ApogeoAPIError) {
      if (error.statusCode === 409) {
        console.error('Email already exists');
      } else {
        console.error('Registration failed:', error.message);
      }
    }
    throw error;
  }
}

// Token refresh helper
async function refreshAuthToken(refreshToken: string) {
  const client = new ApogeoAPI({ token: 'temp' });

  try {
    const newTokens = await client.auth.refreshToken({
      refresh_token: refreshToken
    });

    localStorage.setItem('access_token', newTokens.access_token);
    localStorage.setItem('refresh_token', newTokens.refresh_token);

    return newTokens.access_token;
  } catch (error) {
    // Refresh failed, redirect to login
    localStorage.clear();
    window.location.href = '/login';
  }
}

Geography Queries

Building a Location Selector

import { ApogeoAPI, Country, State, City } from 'apogeoapi';

const client = new ApogeoAPI({ apiKey: process.env.GEO_API_KEY! });

class LocationSelector {
  async getCountries(): Promise<Country[]> {
    return client.geo.getCountries();
  }

  async getStatesForCountry(countryIso2: string): Promise<State[]> {
    return client.geo.getStates(countryIso2);
  }

  async getCitiesForState(stateId: number): Promise<City[]> {
    return client.geo.getCities(stateId, 1000); // Get up to 1000 cities
  }

  async searchLocation(query: string) {
    // Search across all types
    const results = await client.geo.search({
      query,
      limit: 10
    });

    return {
      countries: results.data.filter(r => r.type === 'country'),
      states: results.data.filter(r => r.type === 'state'),
      cities: results.data.filter(r => r.type === 'city')
    };
  }
}

// Usage
const selector = new LocationSelector();

// Cascade dropdowns
const countries = await selector.getCountries();
const states = await selector.getStatesForCountry('US');
const cities = await selector.getCitiesForState(1234);

// Search as user types
const results = await selector.searchLocation('new');
console.log(results); // { countries: [...], states: [...], cities: [...] }

Caching Geography Data

import { ApogeoAPI, Country } from 'apogeoapi';

class CachedGeoClient {
  private cache = new Map<string, any>();
  private ttl = 24 * 60 * 60 * 1000; // 24 hours

  constructor(private client: GeoAPI) {}

  async getCountries(): Promise<Country[]> {
    const cacheKey = 'countries';
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }

    const data = await this.client.geo.getCountries();
    this.cache.set(cacheKey, { data, timestamp: Date.now() });
    return data;
  }

  async getCountryByIso(iso2: string): Promise<Country> {
    const cacheKey = `country:${iso2}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }

    const data = await this.client.geo.getCountryByIso(iso2);
    this.cache.set(cacheKey, { data, timestamp: Date.now() });
    return data;
  }

  clearCache() {
    this.cache.clear();
  }
}

// Usage
const client = new ApogeoAPI({ apiKey: 'xxx' });
const cachedClient = new CachedGeoClient(client);

const countries = await cachedClient.getCountries(); // Fetches from API
const countriesAgain = await cachedClient.getCountries(); // Returns from cache

Account Management

Usage Monitoring Dashboard

import { ApogeoAPI } from 'apogeoapi';

const client = new ApogeoAPI({ apiKey: 'xxx' });

async function buildDashboard() {
  const dashboard = await client.account.getDashboard();

  const usagePercentage = (
    (dashboard.usage.requestsThisMonth / dashboard.usage.monthlyQuota) * 100
  ).toFixed(2);

  console.log('='.repeat(50));
  console.log('ACCOUNT DASHBOARD');
  console.log('='.repeat(50));
  console.log(`User: ${dashboard.user.username} (${dashboard.user.email})`);
  console.log(`Plan: ${dashboard.subscription.tier.toUpperCase()}`);
  console.log(`Status: ${dashboard.subscription.status}`);
  console.log('');
  console.log('Usage:');
  console.log(`  Requests: ${dashboard.usage.requestsThisMonth} / ${dashboard.usage.monthlyQuota}`);
  console.log(`  Percentage: ${usagePercentage}%`);
  console.log(`  Remaining: ${dashboard.usage.remaining}`);
  console.log('');
  console.log('Limits:');
  console.log(`  Rate Limit: ${dashboard.limits.rateLimit} req/min`);
  console.log(`  Max API Keys: ${dashboard.limits.maxApiKeys}`);
  console.log(`  Overage Allowed: ${dashboard.limits.overageAllowed ? 'Yes' : 'No'}`);
  console.log(`  Price: $${dashboard.limits.price}/month`);
  console.log('');
  console.log(`API Keys: ${dashboard.apiKeys.active} / ${dashboard.apiKeys.max}`);
  console.log('='.repeat(50));

  // Alert if quota > 80%
  if (parseFloat(usagePercentage) > 80) {
    console.warn('⚠️  WARNING: You have used more than 80% of your quota!');
  }

  return dashboard;
}

buildDashboard();

API Keys Lifecycle

Rotating API Keys

import { ApogeoAPI } from 'apogeoapi';

const client = new ApogeoAPI({ token: 'user_jwt_token' });

async function rotateApiKey(oldKeyId: string) {
  // 1. Create new key
  const newKey = await client.apiKeys.create({
    name: 'Production Key (Rotated)',
    expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // 1 year
  });

  console.log('🔑 New API key created:', newKey.key);
  console.log('⚠️  Update this in your application NOW!');

  // 2. Wait for confirmation
  console.log('Press Enter after updating your application...');
  await waitForEnter();

  // 3. Revoke old key
  await client.apiKeys.revoke(oldKeyId);
  console.log('✅ Old key revoked');

  // 4. Optional: Delete old key after grace period
  setTimeout(async () => {
    await client.apiKeys.delete(oldKeyId);
    console.log('🗑️  Old key deleted');
  }, 7 * 24 * 60 * 60 * 1000); // 7 days grace period

  return newKey.key;
}

function waitForEnter(): Promise<void> {
  return new Promise(resolve => {
    process.stdin.once('data', () => resolve());
  });
}

Subscription Management

Complete Upgrade Flow

import { ApogeoAPI } from 'apogeoapi';

const client = new ApogeoAPI({ token: 'user_jwt_token' });

async function upgradeSubscription() {
  // 1. Check current tier
  const dashboard = await client.account.getDashboard();
  console.log('Current tier:', dashboard.subscription.tier);

  // 2. Create checkout session
  const checkout = await client.billing.createCheckoutSession(
    'professional',
    'https://myapp.com/upgrade/success',
    'https://myapp.com/upgrade/cancel'
  );

  console.log('Checkout URL:', checkout.url);
  console.log('Session ID:', checkout.sessionId);

  // 3. Redirect user (in browser)
  // window.location.href = checkout.url;

  // 4. After successful payment, Stripe webhook will update subscription
  // You can then verify:
  setTimeout(async () => {
    const updatedDashboard = await client.account.getDashboard();
    console.log('New tier:', updatedDashboard.subscription.tier);
  }, 5000);
}

Webhook Setup

Complete Webhook Implementation

import { ApogeoAPI } from 'apogeoapi';
import express from 'express';
import crypto from 'crypto';

const client = new ApogeoAPI({ token: 'user_jwt_token' });
const app = express();

// 1. Create webhook
async function setupWebhook() {
  const webhook = await client.webhooks.create({
    url: 'https://myapp.com/webhooks/geo-api',
    events: [
      'usage.quota_warning',
      'usage.quota_exceeded',
      'subscription.updated',
      'subscription.canceled'
    ]
  });

  console.log('Webhook ID:', webhook.id);
  console.log('Secret:', webhook.secret);

  // Store secret securely
  process.env.WEBHOOK_SECRET = webhook.secret;

  return webhook;
}

// 2. Validate webhook signature
function validateWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// 3. Handle webhook events
app.post('/webhooks/geo-api', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const payload = req.body.toString();

  // Validate signature
  if (!validateWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }

  // Parse event
  const event = JSON.parse(payload);

  console.log('Webhook event:', event.type);

  // Handle different events
  switch (event.type) {
    case 'usage.quota_warning':
      console.log(`⚠️  Quota warning: ${event.data.usagePercentage}%`);
      // Send notification to user
      break;

    case 'usage.quota_exceeded':
      console.log('🚫 Quota exceeded!');
      // Disable features or notify urgently
      break;

    case 'subscription.updated':
      console.log(`✅ Subscription updated to: ${event.data.tier}`);
      // Update user permissions
      break;

    case 'subscription.canceled':
      console.log('❌ Subscription canceled');
      // Downgrade user to free tier
      break;
  }

  res.status(200).send('OK');
});

// 4. Test webhook
async function testWebhook(webhookId: string) {
  const result = await client.webhooks.test(webhookId);
  console.log('Test result:', result.success);
}

Error Handling Patterns

Comprehensive Error Handler

import { ApogeoAPI, ApogeoAPIError } from 'apogeoapi';

const client = new ApogeoAPI({ apiKey: 'xxx' });

async function robustApiCall<T>(
  operation: () => Promise<T>,
  retries = 3
): Promise<T> {
  for (let i = 0; i < retries; i++) {
    try {
      return await operation();
    } catch (error) {
      if (error instanceof ApogeoAPIError) {
        // Handle specific error codes
        switch (error.statusCode) {
          case 401:
            console.error('Unauthorized - check API key');
            throw error;

          case 403:
            console.error('Forbidden - quota exceeded or insufficient permissions');
            throw error;

          case 404:
            console.error('Not found');
            throw error;

          case 429:
            console.warn(`Rate limited, retrying after delay (attempt ${i + 1})`);
            const retryAfter = error.response?.retryAfter || 1000;
            await new Promise(resolve => setTimeout(resolve, retryAfter));
            continue;

          case 500:
          case 502:
          case 503:
            console.warn(`Server error, retrying (attempt ${i + 1})`);
            await new Promise(resolve => setTimeout(resolve, 2000));
            continue;

          default:
            console.error('API Error:', error.message);
            throw error;
        }
      } else {
        console.error('Unexpected error:', error);
        throw error;
      }
    }
  }

  throw new Error('Max retries exceeded');
}

// Usage
const countries = await robustApiCall(() => client.geo.getCountries());

Production Best Practices

Singleton Client Pattern

// lib/geo-client.ts
import { ApogeoAPI } from 'apogeoapi';

let clientInstance: GeoAPI | null = null;

export function getGeoClient(): GeoAPI {
  if (!clientInstance) {
    if (!process.env.GEO_API_KEY) {
      throw new Error('GEO_API_KEY environment variable is required');
    }

    clientInstance = new ApogeoAPI({
      apiKey: process.env.GEO_API_KEY,
      baseURL: process.env.GEO_API_URL,
      timeout: 30000,
      retries: 3,
      debug: process.env.NODE_ENV === 'development'
    });
  }

  return clientInstance;
}

// Usage across your application
import { getGeoClient } from './lib/geo-client';

const client = getGeoClient();
const countries = await client.geo.getCountries();

Environment-Specific Configuration

// config/geo-api.config.ts
import { SDKConfig } from 'apogeoapi';

const configs: Record<string, SDKConfig> = {
  development: {
    apiKey: process.env.DEV_GEO_API_KEY!,
    baseURL: 'http://localhost:3000/v1',
    timeout: 60000,
    retries: 1,
    debug: true
  },

  staging: {
    apiKey: process.env.STAGING_GEO_API_KEY!,
    baseURL: 'https://staging-api.apogeoapi.com/v1',
    timeout: 30000,
    retries: 3,
    debug: false
  },

  production: {
    apiKey: process.env.PROD_GEO_API_KEY!,
    baseURL: 'https://api.apogeoapi.com/v1',
    timeout: 30000,
    retries: 3,
    debug: false
  }
};

export function getConfig(): SDKConfig {
  const env = process.env.NODE_ENV || 'development';
  return configs[env] || configs.development;
}

For more examples, see the full documentation.