If you have ever built an address form, shipping calculator, or travel app, you know the struggle: you need a reliable way for users to search for their city or state, but you do not want to maintain a multi-gigabyte geospatial database just for an autocomplete dropdown.
Hosting a dedicated GIS database with hundreds of thousands of cities and coordinates can be overkill for most web projects. On the other hand, many public location services have strict rate limits, high latency on keystrokes, or lack fine-grained radius search.
To solve this for my own projects, I built GeoPulse: an API designed specifically for fast city search, radius lookups, and administrative hierarchies with over 800,000 locations worldwide.
In this guide, I will show you how to implement a clean city autocomplete input and how to run radius-based distance queries.
The Data Under the Hood
The dataset behind GeoPulse merges official GeoNames records with Wikidata entities:
- 800,000+ populated places: Global capitals, cities, towns, and municipalities.
- Hierarchical divisions: Country -> Region/State -> Sub-division -> City.
- Coordinates & Timezones: Latitude, longitude, elevation, and real-time local timestamps adjusted for daylight saving time.
1. Building a Fast City Autocomplete
When a user types into an input field, you want to query cities matching the prefix, but only after they stop typing for 250ms (debounce) to avoid wasting requests.
Here is a clean vanilla JavaScript implementation:
<input type="text" id="cityInput" placeholder="Start typing a city (e.g. San, Tok, Lond)..." />
<ul id="results"></ul>
const input = document.getElementById('cityInput');
const list = document.getElementById('results');
let debounceTimer;
input.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
const query = e.target.value.trim();
if (query.length < 2) {
list.innerHTML = '';
return;
}
debounceTimer = setTimeout(() => {
fetchCities(query);
}, 250);
});
async function fetchCities(prefix) {
const url = `https://geopulse-global-cities-geolocation-api1.p.rapidapi.com/geo/cities?namePrefix=${encodeURIComponent(prefix)}&limit=5&sort=-population`;
try {
const res = await fetch(url, {
headers: {
'x-rapidapi-key': 'YOUR_RAPIDAPI_KEY',
'x-rapidapi-host': 'geopulse-global-cities-geolocation-api1.p.rapidapi.com'
}
});
const body = await res.json();
renderResults(body.data || []);
} catch (err) {
console.error(err);
}
}
function renderResults(cities) {
list.innerHTML = cities
.map(c => `<li><strong>${c.name}</strong>, ${c.region} (${c.countryCode}) - Pop: ${c.population.toLocaleString()}</li>`)
.join('');
}
API Response Format
Here is the exact JSON structure returned by the endpoint:
{
"metadata": {
"currentOffset": 0,
"totalCount": 1842
},
"data": [
{
"id": 3448439,
"wikiDataId": "Q174",
"type": "CITY",
"name": "Sao Paulo",
"country": "Brazil",
"countryCode": "BR",
"region": "Sao Paulo",
"regionCode": "SP",
"latitude": -23.5475,
"longitude": -46.63611,
"population": 12400232,
"timezone": "America/Sao_Paulo"
},
{
"id": 3871336,
"wikiDataId": "Q2887",
"type": "CITY",
"name": "Santiago",
"country": "Chile",
"countryCode": "CL",
"region": "Region Metropolitana",
"regionCode": "RM",
"latitude": -33.45694,
"longitude": -70.64827,
"population": 5614000,
"timezone": "America/Santiago"
},
{
"id": 4726206,
"wikiDataId": "Q16552",
"type": "CITY",
"name": "San Antonio",
"country": "United States",
"countryCode": "US",
"region": "Texas",
"regionCode": "TX",
"latitude": 29.42412,
"longitude": -98.49363,
"population": 1434625,
"timezone": "America/Chicago"
}
]
}
Sorting by -population ensures that the most prominent matching cities (like Sao Paulo, Santiago, or San Antonio) show up first.
2. Finding Nearby Cities Within a Radius
Another common problem is: "Show me all cities within 50 miles of a target city."
Instead of downloading math libraries to calculate haversine formulas across 800k records on your server, you can query it in a single API call:
async function getNearbyCities(cityId, radiusMiles = 50) {
const url = `https://geopulse-global-cities-geolocation-api1.p.rapidapi.com/geo/cities/${cityId}/nearbyCities?radius=${radiusMiles}&distanceUnit=MI&limit=3`;
const res = await fetch(url, {
headers: {
'x-rapidapi-key': 'YOUR_RAPIDAPI_KEY',
'x-rapidapi-host': 'geopulse-global-cities-geolocation-api1.p.rapidapi.com'
}
});
const data = await res.json();
return data;
}
API Response Format
{
"metadata": {
"currentOffset": 0,
"totalCount": 87
},
"data": [
{
"id": 3461786,
"wikiDataId": "Q175",
"type": "CITY",
"name": "Guarulhos",
"country": "Brazil",
"countryCode": "BR",
"region": "Sao Paulo",
"distance": 8.74,
"latitude": -23.46278,
"longitude": -46.53333,
"population": 1345364
},
{
"id": 3452925,
"wikiDataId": "Q175",
"type": "CITY",
"name": "Osasco",
"country": "Brazil",
"countryCode": "BR",
"region": "Sao Paulo",
"distance": 9.53,
"latitude": -23.5325,
"longitude": -46.79167,
"population": 699944
}
]
}
Each record returns the exact distance relative to the center city in the requested unit (MI or KM).
3. Calculating Distance Between Two Cities
If you need straight-line geodesic distance between two points (useful for delivery estimates or proximity scoring):
async function getDistance(fromCityId, toCityId) {
const url = `https://geopulse-global-cities-geolocation-api1.p.rapidapi.com/geo/cities/${fromCityId}/distance?toCityId=${toCityId}&distanceUnit=KM`;
const res = await fetch(url, {
headers: {
'x-rapidapi-key': 'YOUR_RAPIDAPI_KEY',
'x-rapidapi-host': 'geopulse-global-cities-geolocation-api1.p.rapidapi.com'
}
});
const result = await res.json();
return result;
}
API Response Format
{
"data": 357.24
}
API Reference & Playground
All 39 endpoints (including country profiles, timezones, currency mappings, and administrative divisions) are documented with live test capabilities:
- RapidAPI Marketplace: GeoPulse on RapidAPI
- Interactive Documentation: GeoPulse Scalar Docs
There is a free tier available on RapidAPI with enough quota to test and build small projects.
If you test it or have ideas for additional fields or filters that would make your workflow easier, drop a comment below!
Top comments (0)