How to Use FreeIPAPI in Your Rust Project (with Caching)
If you’ve ever needed IP geolocation data in a Rust application, you know how powerful it is for personalization, fraud prevention, and analytics. FreeIPAPI is a fast and reliable free IP geolocation API that provides accurate IP-based data such as the country, city, ASN, timezone, currency, and more — all at zero cost.
In this guide, we’ll walk through integrating FreeIPAPI into a Rust project, and then optimize it with caching to avoid unnecessary API requests.
Why Use FreeIPAPI?
FreeIPAPI provides detailed location data from an IP address with a single simple request:
https://free.freeipapi.com/api/json/{ip}
Example response:
{
"ipVersion": 6,
"ipAddress": "2a02:8071:1212:aa0:d914:e794:457d:1212",
"latitude": 51.2217,
"longitude": 6.77616,
"countryName": "Germany",
"countryCode": "DE",
"cityName": "Düsseldorf",
"regionName": "North Rhine-Westphalia",
"currencies": ["EUR"],
"asn": "3209",
"asnOrganization": "Vodafone GmbH",
"isProxy": false
}
That’s a lot of geo-data from just one API call!
Why Cache IP Lookups in Rust?
If you make an external API call on every request, you risk:
- Slow performance (due to network latency)
- API overuse or rate limits
- Duplicated work for the same IP
Since IP geolocation data usually doesn’t change within hours or days, it makes sense to store it in a cache.
In Rust, you can implement caching easily with the cached
crate or a custom HashMap
protected by tokio::sync::RwLock
.
Setting Up a Rust Project
First, let’s create a new project:
cargo new freeipapi-demo
cd freeipapi-demo
Add dependencies to Cargo.toml
:
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
cached = "0.46"
Defining the IP Data Struct
We’ll map the JSON response to a Rust struct with Serde:
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct IpData {
pub ipVersion: Option<u8>,
pub ipAddress: Option<String>,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
pub countryName: Option<String>,
pub countryCode: Option<String>,
pub cityName: Option<String>,
pub regionName: Option<String>,
pub currencies: Option<Vec<String>>,
pub asn: Option<String>,
pub asnOrganization: Option<String>,
pub isProxy: Option<bool>,
}
Implementing the Lookup Service with Cache
Here we use the cached
crate to automatically store results for 1 hour (3600 seconds):
use cached::proc_macro::cached;
use reqwest::Error;
mod model;
use model::IpData;
// Caching function: cache up to 10,000 entries for 1 hour
#[cached(size = 10000, time = 3600)]
pub async fn get_ip_data(ip: String) -> Result<IpData, Error> {
let url = format!("https://free.freeipapi.com/api/json/{}", ip);
let response = reqwest::get(&url).await?;
let data = response.json::<IpData>().await?;
Ok(data)
}
The #[cached]
macro makes sure repeat calls for the same IP within 1 hour will be returned directly from memory, with no external API call needed.
Example App: Lookup Current Request IP
Let’s try it in a small test program:
#[tokio::main]
async fn main() {
let ip = "8.8.8.8"; // Example: Google DNS
match get_ip_data(ip.to_string()).await {
Ok(data) => {
println!("Country: {:?}", data.countryName);
println!("City: {:?}", data.cityName);
println!("ASN: {:?}", data.asn);
}
Err(err) => eprintln!("Failed to fetch IP data: {:?}", err),
}
}
When you run it with:
cargo run
You’ll see something like:
Country: Some("United States")
City: Some("Mountain View")
ASN: Some("15169")
And if you call again with the same IP, it retrieves it from cache instantly. 🚀
Conclusion
Integrating FreeIPAPI into a Rust project is straightforward with reqwest
and serde
. By adding caching via the cached
crate, you improve performance and avoid redundant external calls.
Whether you’re building Rust applications for fraud detection, analytics, or personalization, FreeIPAPI provides accurate and free geolocation data, while Rust ensures your implementation is fast and memory-safe.