Free GeoIP API with Cloudflare Workers

A nice feature of Google Maps is to display a map of your region whenever you visit maps.google.com. This is useful because you likely want to look at a map of something nearby instead of on the other side of the world. The way Google knows your general location is because it knows what IP address you’re requesting the map from and it has a database mapping IP addresses to geographic coordinates. This process is called Geolocation, or more specifically GeoIP lookup.

There are free GeoIP API services out there. Here is an example of an IP lookup and response

const response = await fetch(`http://ip-api.com/json/${ip}?fields=lat,lon`);

// response JSON
{
    "lat": 47.6722,
    "lon": -122.1257
}

But because an IP can only be resolved server side and because browsers do not allow cross-domain requests, you have to query ip-api.com on the server side or use a Cloudflare worker.

Or… you can just use Cloudflare workers to do the GeoIP lookup. It turns out that a Cloudflare worker request already has some custom headers containing the user’s latitude and longitude among other things like country and ISP provider. This information is contained in the request object under request.cf. Here is just an abridged version of what the cf object contains:

"cf": {
  /* Many fields omitted for brevity */
  "longitude": "37.61710",
  "latitude": "55.74830",
  "country": "RU",
  "city": "Moscow",
  "postalCode": "127976"
}

You can see the full cf fields available by looking at the Cloudflare worker logs.

Using the Cloudflare information is better because it does not require a proxy call to another service, the locations are much more accurate (in my experience), and a lot of free GeoIP services are only free for non-commercial use.

Here is the full source code for a GeoIP lookup worker:

addEventListener("fetch", (event) => {
    const { request } = event;
    // use a POST to prevent crawling
    if (request.method !== "POST") {
        return event.respondWith(new Response('Method not allowed', { status: 405 }));
    }

    event.respondWith(
        handleRequest(request).catch(
            (err) => new Response(err.stack, { status: 500 })
        )
    );
});

async function handleRequest(request) {
    const lat = request.cf.latitude;
    const lon = request.cf.longitude;
    const newResponse = new Response(JSON.stringify({ lat, lon }), {
        status: 200,
        statusText: "OK",
        headers: new Headers([
            ['Content-Type', 'application/json; charset=utf-8']
        ]),
    });

    return newResponse;
}

I use this Worker to display localized maps on shademap.app.

Thanks for reading.

 
16
Kudos
 
16
Kudos

Now read this

Ghosts in the machine

”One of the biggest differences between hobbyists and professional programmers is the difference that grows out of moving from superstition to understanding.” -Stephen McConnell In my early days of programming I often fell into the trap... Continue →