Your Own GeoIP SaaS Server
Saad Alkentar

Saad Alkentar @saad4software

About: Developer, not a coder; Lazy, when it comes to repeating myself; Doer, more than thinker; Adventurer, eager to try new things

Joined:
Oct 2, 2024

Your Own GeoIP SaaS Server

Publish Date: Aug 12
0 0

In modern web development, there's often a need to determine a user's geographical location without using the browser's Geolocation API, which requires explicit user permission. A common alternative is using GeoIP, which maps a user's IP address to a physical location.

While this is a powerful and reliable method, it's important to be aware of the costs. Many GeoIP service providers charge based on the number of API requests, and for high-traffic applications, these costs can escalate significantly.

Here's a breakdown of some popular GeoIP providers and their approximate monthly costs for a bundle of one million requests:

The Idea

In this tutorial, we're going to build our own GeoIP server. I'll walk you through setting up the core functionality of a GeoIP SaaS application using Django, the GeoIP2 library, and the DB-IP database. While this tutorial will focus on the main GeoIP feature, a separate tutorial will cover the basic setup of a SaaS server.
Let's dive in!

Django project setup

Let's start by creating the Django project and the apps for accounts, admin, and main functionality app

# install Django lib and extensions
pip install Django
pip install django-filter
pip install djangorestframework
pip install djangorestframework_simplejwt
pip install pillow

pip install geoip2

# create the project
django-admin startproject saas
cd saas
python manage.py startapp app_account
python manage.py startapp app_admin
python manage.py startapp app_main
Enter fullscreen mode Exit fullscreen mode

The next step is to setup GeoIP2 library in the project settings

...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework', # for restful api
    'django_filters',
    'app_account', # the apps
    'app_admin',
    'app_main',
]
...
GEOIP_PATH =os.path.join(BASE_DIR, 'geoip') # the location of ip db
Enter fullscreen mode Exit fullscreen mode

saas/settings.py

Let's create a folder in the main app path with the name 'geoip' to store the IP databases in the next step.

PS. I highly recommend using the account management app from previous tutorials to save time and effort later on.

DP-IP datasets

Now that we have our project ready, we can download IP databases from DP-IP. It has multiple free "IP Geolocation Databases" that update monthly. Let's start by downloading the database we want to use from

https://db-ip.com/db/lite.php

We will use IP to city db, after agreeing with the licensing terms, make sure to download the MMDB version of the database. It was about 60MB at the time of writing this tutorial.

Extract the downloaded MMDB file, rename it to GeoLite2-City.mmdb, and make sure to put it in the geoip folder in the project folder

GeoIP implementation testing

Let's check if it works. Firstly, we need to migrate the dataset and make sure the project works properly

python manage.py makemigrations
python manage.py migrate
python manage.py runserver 0.0.0.0:8555
Enter fullscreen mode Exit fullscreen mode

This should run the project on port 8555. If it works successfully, we can

python manage.py shell
Enter fullscreen mode Exit fullscreen mode
In [1]: from django.contrib.gis.geoip2 import GeoIP2                                     

In [2]: g = GeoIP2()                                                                     

In [3]: g.city("72.14.207.99")                                                           
Out[3]:                                                                                  
{'city': 'Mountain View',
 'continent_code': 'NA',
 'continent_name': 'North America',
 'country_code': 'US',
 'country_name': 'United States',
 'dma_code': None,
 'is_in_european_union': False,
 'latitude': 37.4225,
 'longitude': -122.085,
 'postal_code': None,
 'region': None,
 'time_zone': None}

In [4]: 
Enter fullscreen mode Exit fullscreen mode

Great! Now that we know it works, let's create a simple API where users send the IP and we answer with its location.

GeoIP API

To simplify this step, let's skip using serializers and pass the IP as a GET parameter, in the views file

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from common.utils import *

from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception

class GeoIPView(APIView):
    permission_classes = ( )
    renderer_classes = [CustomRenderer, BrowsableAPIRenderer]

    def get(self, request, *args, **kwargs):
        ip_address = request.query_params.get('ip', None)
        if not ip_address:
            raise APIException("The 'ip' query parameter is required.")

        try:
            g = GeoIP2()
            # You can use .city(ip), .country(ip), etc. depending on the data you need
            geoip_data = g.city(ip_address)
            return Response(geoip_data)
        except GeoIP2Exception:
            # This exception is raised if the IP address is not in the database.
            raise APIException(f"Information for IP address '{ip_address}' not found.")

Enter fullscreen mode Exit fullscreen mode

app_main/views.py

We are getting the IP from GET parameters, then passing it to GeoIP, and responding with its direct response.
Let's assign a URL for the view

from django.urls import path, include
from .views import *

urlpatterns = [
    path('geoip/', GeoIPView.as_view()),
]
Enter fullscreen mode Exit fullscreen mode

app_main/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('app_main.urls')),
]
Enter fullscreen mode Exit fullscreen mode

saas/urls.py

Let's try it out!

GeoIP api

great!
Let's do something more entertaining, let's edit the API to respond with the user's IP location if the IP parameter is not provided!

from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception

class GeoIPView(APIView):
    permission_classes = ( )
    renderer_classes = [CustomRenderer, BrowsableAPIRenderer]

    def _get_client_ip(self, request):
        """Helper method to get the client's real IP address from the request."""
        # Check for the X-Forwarded-For header, which is used by proxies
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            # The header can contain a comma-separated list of IPs; the first is the client
            ip = x_forwarded_for.split(',')[0]
        else:
            # If not behind a proxy, use the standard REMOTE_ADDR
            ip = request.META.get('REMOTE_ADDR')
        return ip

    def get(self, request, *args, **kwargs):
        ip_address = request.query_params.get('ip', None)
        if not ip_address:
            ip_address = self._get_client_ip(request)


        try:
            g = GeoIP2()
            # You can use .city(ip), .country(ip), etc. depending on the data you need
            geoip_data = g.city(ip_address)
            return Response(geoip_data)
        except Exception as e:
            # This exception is raised if the IP address is not in the database.
            raise APIException(f"Information for IP address '{ip_address}' not found.")

Enter fullscreen mode Exit fullscreen mode

app_main/views.py

We start by looking for ip parameter in the GET request; if it exists, we look for it; if not, we will try to get the request IP.
_get_client_ip will get the request IP, locally it will get the local IP, if online, it will get the user request IP.

Let's try it online

GET /api/geoip/

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status": "success",
    "code": 200,
    "data": {
        "city": "Remscheid",
        "continent_code": "EU",
        "continent_name": "Europe",
        "country_code": "DE",
        "country_name": "Germany",
        "dma_code": null,
        "is_in_european_union": true,
        "latitude": 51.1798,
        "longitude": 7.1925,
        "postal_code": null,
        "region": null,
        "time_zone": null
    },
    "message": null
}

Enter fullscreen mode Exit fullscreen mode
GET /api/geoip/

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status": "success",
    "code": 200,
    "data": {
        "city": "Nicosia",
        "continent_code": "EU",
        "continent_name": "Europe",
        "country_code": "CY",
        "country_name": "Cyprus",
        "dma_code": null,
        "is_in_european_union": true,
        "latitude": 35.1728,
        "longitude": 33.354,
        "postal_code": null,
        "region": null,
        "time_zone": null
    },
    "message": null
}

Enter fullscreen mode Exit fullscreen mode

Bonus, User IP 🤓

We can add the user IP to the response by adding one line to the view as

...
        try:
            g = GeoIP2()
            # You can use .city(ip), .country(ip), etc. depending on the data you need
            geoip_data = g.city(ip_address)
            geoip_data['ip'] = ip_address
            return Response(geoip_data)
        except Exception as e:
            # This exception is raised if the IP address is not in the database.
            raise APIException(f"Information for IP address '{ip_address}' not found.")

Enter fullscreen mode Exit fullscreen mode

app_main/views.py

the response

GET /api/geoip/

HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "status": "success",
    "code": 200,
    "data": {
        "city": "Beirut",
        "continent_code": "AS",
        "continent_name": "Asia",
        "country_code": "LB",
        "country_name": "Lebanon",
        "dma_code": null,
        "is_in_european_union": false,
        "latitude": 33.8938,
        "longitude": 35.5018,
        "postal_code": null,
        "region": null,
        "time_zone": null,
        "ip": "185.227.133.12"
    },
    "message": null
}
Enter fullscreen mode Exit fullscreen mode

That is it for this tutorial. We still need to add the SaaS users/ tokens/ stats management, which we will discuss in another tutorial, so

Stay tuned 😎

Comments 0 total

    Add comment