DEV Community

Cover image for Your Own GeoIP SaaS Server
Saad Alkentar
Saad Alkentar

Posted on

Your Own GeoIP SaaS Server

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 😎

Top comments (0)