React NativeExpoMobileGeolocationTutorial

IP Geolocation in React Native and Expo — Detect Country Without Exposing API Keys

ApogeoAPI5 min read

⚠️ Never put API keys in mobile apps

Anyone can extract API keys from a mobile app bundle using tools like strings or a proxy debugger. If your API key is in a React Native/Expo bundle, it will be stolen and used by others. Always call geolocation APIs from your own backend.

Architecture: mobile app → your backend → ApogeoAPI

React Native App
      │
      │ GET /api/geo   (your own endpoint — no key)
      ▼
Your Backend (Node/Express, Next.js API route, etc.)
      │
      │ GET /v1/ip/:clientIp   (with X-API-Key header)
      ▼
ApogeoAPI

Step 1: Backend proxy (Next.js API route)

// pages/api/geo.ts  (or app/api/geo/route.ts for App Router)
import type { NextApiRequest, NextApiResponse } from 'next';

const API_KEY = process.env.APOGEOAPI_KEY ?? '';
const cache = new Map<string, { data: object; ts: number }>();
const TTL = 4 * 60 * 60 * 1000;

function subnet(ip: string) { return ip.split('.').slice(0, 3).join('.'); }

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const ip =
    (req.headers['x-forwarded-for'] as string)?.split(',')[0].trim() ??
    req.socket.remoteAddress ??
    '0.0.0.0';

  const key = subnet(ip);
  const hit = cache.get(key);
  if (hit && Date.now() - hit.ts < TTL) {
    return res.status(200).json(hit.data);
  }

  const apiRes = await fetch(`https://api.apogeoapi.com/v1/ip/${ip}`, {
    headers: { 'X-API-Key': API_KEY },
  });
  const data = await apiRes.json();
  cache.set(key, { data, ts: Date.now() });
  res.status(200).json(data);
}

Step 2: React Native hook

// hooks/useGeo.ts
import { useState, useEffect } from 'react';

export interface GeoData {
  country_code: string;
  country_name: string;
  city?: string;
  timezone?: string;
  currency?: string;
}

const CACHE_KEY = '@geo_data';
const TTL_MS = 4 * 60 * 60 * 1000;

export function useGeo() {
  const [geo, setGeo] = useState<GeoData | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    (async () => {
      // Try AsyncStorage cache first
      try {
        const AsyncStorage = (await import('@react-native-async-storage/async-storage')).default;
        const raw = await AsyncStorage.getItem(CACHE_KEY);
        if (raw) {
          const { data, ts } = JSON.parse(raw);
          if (Date.now() - ts < TTL_MS) {
            setGeo(data);
            setLoading(false);
            return;
          }
        }
      } catch {}

      // Call YOUR backend — not ApogeoAPI directly!
      try {
        const res = await fetch('https://yourapp.com/api/geo');
        const data: GeoData = await res.json();
        setGeo(data);
        const AsyncStorage = (await import('@react-native-async-storage/async-storage')).default;
        await AsyncStorage.setItem(CACHE_KEY, JSON.stringify({ data, ts: Date.now() }));
      } catch {
        setGeo({ country_code: 'US', country_name: 'United States' });
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  return { geo, loading };
}

Step 3: Use in any component

// screens/HomeScreen.tsx
import { View, Text } from 'react-native';
import { useGeo } from '../hooks/useGeo';

const PRICE_USD = 29;

export default function HomeScreen() {
  const { geo, loading } = useGeo();

  if (loading) return <Text>Loading...</Text>;

  const price = geo?.currency === 'EUR'
    ? '€26'
    : geo?.currency === 'GBP'
    ? '£22'
    : `$${PRICE_USD}`;

  return (
    <View>
      <Text>Hello from {geo?.country_name ?? 'the world'}!</Text>
      <Text>Premium: {price}/month</Text>
      {geo?.country_code === 'US' && (
        <Text>🇺🇸 Free shipping on orders over $50!</Text>
      )}
    </View>
  );
}

Expo: use in an Expo Router app

// app/(tabs)/index.tsx (Expo Router v3)
import { useGeo } from '../../hooks/useGeo';

export default function Index() {
  const { geo, loading } = useGeo();
  // Same usage as above
}

Why not GPS instead?

GPS gives precise location but requires a location permission prompt, drains battery, and may fail indoors. IP-based country detection requires no permissions, works instantly, and is accurate enough for: currency display, content localisation, shipping zone selection, and GDPR consent detection. Use GPS only when you need precise coordinates (maps, navigation, nearby search).

Try ApogeoAPI free

1,000 requests/month forever. 14-day full-access trial. No credit card.

Get your free API key