When most people think of geolocation, they remember the familiar pop-up notifications saying that the “website wants to know your location”. But did you know that you can get geolocation information about your visitors with the IP address alone, without requiring any extra permissions?
To web developers, geolocation is a treasure trove of
information and capabilities that can enhance the visitor’s experience and
provide meaningful information to the developer. While the HTML5 Geolocation
API can certainly provide developers with the actual GPS
coordinates of the visitor’s device, developers more commonly need a broader set of location data. They
can get that information using the visitor’s IP address.
By itself, an IP address is simply that: an
address of a device on the internet. However, the World Wide Web is well over
20 years old, and over that large span of time, tons of data has been
collected, correlated, and combined in relation to millions of IP addresses. While
there are many third-party services that offer IP geolocation data, none are as
accurate or reliable as ipdata. Before we look at how to use ipdata’s service,
let’s first look at why you may want to use geolocation data.
Uses of Geolocation
There are many legitimate reasons why you would want to gather geolocational data on your visitors and use it in
your applications.
For one, you can serve regional content based upon your visitor’s IP
address. Does your website offer products globally but services locally? With
IP geolocational data, you can serve your normal, product-based content and
target local visitors with special content about your services.
Using
geolocational data can also help you identify visitors that fall under the EU’s
General Data Protection Regulation so that you can properly adhere to the GDPR
for those visitors.
Why
ipdata?
With ipdata, you can access an accurate IP address
intelligence API, allowing you to look up the approximate location of any IP
address. But on top of that, ipdata also provides aggregated open-source threat
intelligence data and IP to company lookups, as well as internationalization data
like currencies, time zones, and languages. Many organizations and companies—NASA,
Comcast, and Disney, just to name a few—trust ipdata with the geolocational data
they provide.
While you can use any HTTP client to connect to and use
their RESTful API, ipdata provides open-source libraries for C#, Java, PHP,
JavaScript, and Python to make working with the API and response data a breeze.
Third-party libraries are available for Ruby, Swift, and Go.
Note: this article uses ipdata’s JavaScript
library in a Node application, but the same ideas and concepts can be applied
to other languages. Refer to the official ipdata library documentation.
Using ipdata’s API
The
first thing we need is a Node application; be sure to install the latest Node
LTS version from the Node.js website. Create a new directory called tutsplus-ipdata
for
your application’s project. Then open your operating system’s terminal
application, navigate to your newly created directory, and type the following
to initialize the project:
npm init -y
We now need to choose our application framework. Yes, we don’t technically need one,
but frameworks like Express and Hapi greatly increase our productivity when
building Node-based web applications. We’ll use Hapi, so we need to install it,
as well as the ipdata package, with the following command:
npm install @hapi/hapi ipdata --save
In a real application, you’d naturally want to install
packages for handling static files, views, errors, and so on. Hapi and ipdata
are the only packages we’ll need for this tutorial because our server will be
extremely simple: it will only respond with text.
Writing the Server
If you’re new to Hapi, the process for creating and initializing the server is straightforward. First, create a new file called index.js
and create the server with the following code:
const Hapi = require('@hapi/hapi'); const server = Hapi.server({ port: 3000, host: 'localhost' });
The first line of this code imports the Hapi
object, which we use to create our server by calling its server()
method. The server()
method accepts an object that allows us to configure our server. In the above code, we configure our server to run on the localhost and listen on port 3000.
Next, we set up our single route using the server’s route()
method:
server.route({ method: 'GET', path: '/', handler: (request, h) => { return 'Hello, Tuts+'; } });
This route handles GET requests for the home, or root, of our application. The handler function executes, returning a simple string, when the server receives a request that matches this route.
Lastly, we start the server with the following code:
await server.start(); console.log('Server running on %s', server.info.uri);
Because the server’s start()
method is asynchronous, we can choose how we want to use it. This code uses the await
keyword, which means we have to execute this code within a function marked as async
. So we’ll wrap the server creation, configuration, and execution within an asynchronous function, like this:
const Hapi = require('@hapi/hapi'); const init = async () => { const server = Hapi.server({ port: 3000, host: 'localhost' }); // register plugins here server.route({ method: 'GET', path: '/', handler: (request, h) => { return 'Hello, Tuts+'; } }); await server.start(); console.log('Server running on %s', server.info.uri); }; init();
This code creates a simple server that doesn’t do anything except output “Hello, Tuts+” when you view http://localhost:3000 in your browser. Test it for yourself by running node index.js
in your terminal.
We can add some useful functionality to this simple test server by writing and registering a plugin that only allows requests from certain countries—a whitelist.
Using ipdata for a Whitelist
The geolocation data returned by ipdata’s API contains a host of information that we can use to create a whitelist (or blacklist, if you want to take the opposite approach). To name just a few: you can retrieve the city, region/state, country, continent, ISP, and hosting provider that are associated with an IP address. For our purposes, we’ll whitelist requests coming from certain countries.
Create a new file called whitelist.js
, and type these first two lines:
const IPData = require('ipdata').default; const client = new IPData('your API key')
The first line of this code imports the IPData
constructor function, which is used to create the ipdata API client. The second line creates the client, passing in the API key associated with your account. If you do not have an account, you can sign up for a free account from ipdata. You’ll receive your API key via email. You can use the string “test
” for testing purposes, but be aware it is rate limited.
Now define the plugin with the following code:
exports.plugin = { name: 'whitelist', register: async function(server, options) { // register server things here } };
This code demonstrates the boilerplate for a basic Hapi plugin. It exports the plugin object and defines the plugin’s name property and register()
method.
Unlike Express, Hapi doesn’t use, or even have the concept of, middleware. Instead, you hook into lifecycle events to process incoming requests, and you do so using the server’s ext()
method, as shown in the following code:
server.ext('onRequest', async (request, h) => { // process request data here });
This code goes inside the plugin’s register()
method, and it sets up an event handler for the onRequest
lifecycle event. The asynchronous function provided to ext()
will handle the event and allow you to process the request.
We’ll start the processing by defining our whitelist of countries, which is an array of string values that contain the country codes of our whitelisted countries. Then, we’ll retrieve the requester’s IP address and use it to query the ipdata API, as shown here:
server.ext('onRequest', async (request, h) => { const whitelist = ['US', 'CA', 'UK']; const ip = '104.17.233.79'; //request.info.remoteAddress; const locData = await client.lookup(ip); // check the whitelist here });
Note that if this application is running on your machine, the requesting IP address is the loopback IP address for your machine, and there is no geolocational data associated with that IP address. Instead, the above code uses the IP address for the Envato Tuts+ website.
The ipdata client is simple to use—its lookup()
method accepts a number of arguments, the most notable being the desired IP, and queries ipdata’s API with the provided information.
The response from lookup()
is an object that contains a wide range of properties, depending upon the result of the query. For example, the response object always contains a property called status
that indicates if the provided IP address exists within ipdata’s database. It is an HTTP status code, so it’s a good idea to check its value before doing anything else.
The property we are concerned with is country_code
, and we can use it to check our whitelist. The following code uses both the status
and country_code
properties:
server.ext('onRequest', async (request, h) => { const whitelist = ['US', 'CA', 'UK']; const ip = '104.17.233.79'; //request.info.remoteAddress; const locData = await client.lookup(ip); if (locData.status === 200 && whitelist.includes(locData.country_code)) { return h.continue; } return h.response('You are not allowed.').code(403).takeover(); });
If the query status is 200 and the country code exists in the whitelist, then we tell our server to continue processing the request with the next plugin or the handler for the route—essentially allowing the requester to access the resource they requested. Otherwise, we takeover
the response with a 403 status.
As previously mentioned, the query result object contains a wide variety of properties, but we only need one: country_code
. As such, we can increase network efficiency by telling the lookup()
method that we only want a certain set of properties, like this:
const locData = await client.lookup(ip, null, ['country_code']);
This code now passes an array that contains only the properties that we need (the status
property is always included, so we do not need to specify it in the array). With this change, the completed plugin looks like the following:
const IPData = require('ipdata').default; const client = new IPData('your API key'); exports.plugin = { name: 'whitelist', register: async function(server, options) { server.ext('onRequest', async (request, h) => { const whitelist = ['US', 'CA', 'UK']; const ip = '104.17.233.79'; //request.info.remoteAddress; const locData = await client.lookup(ip, null, ['country_code']); if (locData.status === 200 && whitelist.includes(locData.country_code)) { return h.continue; } return h.response('You are not allowed.').code(403).takeover(); }); } };
Regsitering the Plugin
Now we need to register our whitelist plugin. Before defining our routes in index.js
, add the following line:
await server.register(require('./whitelist));
The complete server code is as follows:
const Hapi = require('@hapi/hapi'); const init = async () => { const server = Hapi.server({ port: 3000, host: 'localhost' }); await server.register(require('./whitelist')); server.route({ method: 'GET', path: '/', handler: (request, h) => { return 'Hello, Tuts+'; } }); await server.start(); console.log('Server running on %s', server.info.uri); }; init();
If you haven’t already, kill the server running in your terminal with Control-C, and run node index.js
again. If you used the Envato Tuts+ website’s IP, you should see the text “Hello, Tuts+” in the browser. If you used an IP address from a country not listed in the whitelist, or you used the IP address from the local machine, then you’ll see the text “You are not allowed“.
Conclusion
For web developers, ipdata provides an invaluable service. Not only does it allow you a sense of who your visitors are, but it lets you incorporate region-specific content to create a meaningful experience for your visitors. It’s free to get started and easy to use, so give ipdata a try today!