Building the R Package ipapi.r: A Journey to Simplify IP Address Lookups

IPQuery API IP Rlang Package ipapi.r

Introduction: The Motivation Behind the Project

ipquery examplee
Check in GitHub

As data analysis increasingly relies on location-based insights, the need to retrieve detailed information about IP addresses—such as geographic location, internet service provider (ISP), and timezone—has grown significantly.

with these information, user achive “security work” like detecting Spam, Fraudsters, or Blocking webcrawlers.

However, R (or shiny) is basically not intended to those work instead of statistical things. This gap inspired the creation of {ipapi.r}, an R package designed to streamline IP address lookups using the IPQuery API.

Introducing {ipapi.r}

What is {ipapi.r}?

{ipapi.r} is an R package that allows users to retrieve detailed information about IP addresses, including country, city, region, ISP, and more informations with just a few lines of code.

Key Features

  • Single or batch IP address lookups.
  • Support for API key integration to handle higher request limits.
  • Easy-to-use functions that return structured data frames, json, yaml and so on.
# install.packages("pak")
pak::pak("jahnen/ipapi.r")

library(ipapi.r)
# Getting Your Own IP
IPQuery()

# Query Own IP
IPQuery("self")

# Query details for a specific IP
IPQuery("1.1.1.1")
# Querying Multiple IPs
IPQuery(c("1.1.1.1", "2.2.2.2")))

The Development Process

Using this {ipapi.r} package is straightforward because the original API is well designed.

So now I’m going to walk you through the process of creating this R package.

File > New project > New Directory > R package > Create

ipquery examplee

or you can use usethis::create_package().

Implement API in R as functions

the original IPQuery API has 3 main functions. See docs

  1. Request (user’s) IP address
  2. Request Specific IP address (like 1.1.1.1)
  3. Request Multiple IP addresses (like 1.1.1.1 , 2.2.2.2)

Also result can have various format: Plain Text, XML, YAML, JSON

Getting this result in R can be done by using httr2 package.

So let’s make first funciton, that returns user’s IP address.

It will be like this:

request https://api.ipquery.io/ and return result.

library(httr2)

IPQuery <- function(){
    "https://api.ipquery.io/" |>
    request() |>
    req_perform() |>
    resp_body_string()
}

However, using library in R package is not recommended. so let’s call it as using roxygen2 (@importFrom or import)

#' @importFrom httr2 request req_perform resp_body_string
#'

and it will return user’s (my blurred) IP address.

ipquery examplee

Next, we need to make function that returns specific IP address. Which can be done by accessing same URL but giving IP as query parameter. So we need to change function to get input (the IP address)

# "https://api.ipquery.io/1.1.1.1"
IPQuery <- function(ip = NULL){
  paste0("https://api.ipquery.io/", ip) |>
    request() |>
    req_perform() |>
    resp_body_string()
}

Note that, IP is not required, (for get self IP address) so use default value as NULL.

ipquery examplee

Now, we need to make function that returns multiple IP addresses. So we need to implement code for handling multiple IP addresses using lapply and return as list.

Assume that input IP has format of vector like c("1.1.1.1", "2.2.2.2").

# https://api.ipquery.io/1.1.1.1,2.2.2.2
IPQuery <- function(ip = NULL) {
  baseURL <- "https://api.ipquery.io/"

  if (is.null(ip)) {
    ip <- ""
  } else if (length(ip) > 1) {
    return(lapply(ip, function(single_ip) {
      query(paste0(baseURL, single_ip))
    }))
  } else if(ip == 'self'){
    ip <- IPQuery()
  }
  resp <- query(paste0(baseURL, ip))
  return(resp)
}

query <- function(i) {
  i |>
    request() |>
    req_perform() |>
    resp_body_string()
}

Note that, query function used in IPQuery function is just for handling single IP address.

ipquery examplee

And finally, we need to handle the result format. which is just text now. but we can add format parameter to handle it. (yaml, json, xml)

# https://api.ipquery.io/1.1.1.1,2.2.2.2?format=xml

IPQuery <- function(ip = NULL, format = "text") {
  baseURL <- "https://api.ipquery.io/"
  format_query <- ifelse(format == "text", "", paste0("?format=", format))

  if (is.null(ip)) {
    ip <- ""
  } else if (length(ip) > 1) {
    return(lapply(ip, function(single_ip) {
      query(paste0(baseURL, single_ip, format_query))
    }))
  } else if(ip == 'self'){
    ip <- IPQuery()
  }
  resp <- query(paste0(baseURL, ip, format_query))
  return(resp)
}
ipquery examplee

Add unit test

Finally, we’ve implemented very basic function of IPQuery api in R. So let’s make testing for this with {testthat}. (usethis::use_testthat())

It’s hard to define an exact test, but the {ipapi.r} package provides a core functionality.

  1. for each format,
  2. for each format. 3. for each example IP (1.1.1.1, 2.2.2.2),
  3. and if it gives an error when entering a non-IP value.
test_that("query works", {
  expect_equal(substr(query("1.1.1.1"), 1, 15), "<!DOCTYPE html>")
})

test_that("error with invalid url", {
  expect_error(query("invalid_url"))
})

test_that("IPQuery text format", {
  expect_equal(IPQuery(), query("https://api.ipify.org/"))
  expect_equal(substr(IPQuery("self"), 1, 5), "{\"ip\"")
  expect_equal(substr(IPQuery(c("1.1.1.1")), 1, 14), "{\"ip\":\"1.1.1.1")
  expect_equal(length(IPQuery(c("1.1.1.1", "2.2.2.2"))), 2)
})

# Skip for other formats, see https://github.com/jahnen/ipapi.r/blob/main/tests/testthat/test-IPQuery.R

Conclusion

{ipapi.r} is a wrapper R package for R users who need to retrieve IP address information quickly and efficiently. By addressing common challenges and incorporating user feedback, the package continues to evolve, offering a reliable solution for location-based data analysis.

Future Plans

since IPQuery api is simple and powerful, There’s not much we can do in {ipapi.r}.

Below may be considered, but you can always make suggestion:

  • Seamless connectivity with packages like {jsonlite} to better utilize each format.
📦 Ask R package development