Geo & Search

Building nearest-me search without a maps API bill

Location-based discovery doesn't require a metered maps platform for every query. Here is how we built geo-search for a multi-city rental marketplace on a fixed hosting cost.

By Team WebSync · · 4 min read

Abstract map grid with a radiating search radius around a central location pin

"Show me listings near me" sounds like a one-line feature. In practice it's a query pattern most databases aren't built for, and a pricing trap if you reach for a metered geolocation API on every search and every page load.

We ran into this building a co-living rental marketplace live across three cities. Renters filter by neighbourhood constantly - on the initial search, and again every time they adjust a radius or move the map. That's not a handful of geocoding calls a day, it's thousands, and a per-request maps bill scales directly with your traffic instead of your revenue.

1. Geocode once, at write time

The expensive part of geo-search is turning an address into coordinates - not searching by coordinates. So we only geocode once, when a listing is created or edited, and store latitude/longitude directly on the property record. Every search afterwards is a pure database query against numbers already on disk, with zero API calls.

The one exception is the user's own search location, which we resolve client-side from the browser's Geolocation API (free) or a typed address geocoded on submit - a few requests per session, not one per listing rendered.

Geocoding is a write-time cost, not a read-time one. If you're calling a maps API inside a search endpoint, you're paying for the same coordinates over and over.

2. Nearest-neighbour without a specialised database

You don't need PostGIS or Elasticsearch to get correct, fast "nearest to me" results at marketplace scale. A bounding-box pre-filter (latitude/longitude between a min and max, sized to roughly the search radius) narrows a full table down to a few hundred candidate rows using ordinary indexed range queries. The Haversine distance formula then runs only against that narrowed set, in application code, to get an accurate distance and sort order.

function boundingBox(lat, lng, radiusKm) {
  const latDelta = radiusKm / 111; // ~111km per degree latitude
  const lngDelta = radiusKm / (111 * Math.cos(lat * Math.PI / 180));
  return {
    minLat: lat - latDelta, maxLat: lat + latDelta,
    minLng: lng - lngDelta, maxLng: lng + lngDelta,
  };
}

function haversineKm(lat1, lng1, lat2, lng2) {
  const R = 6371;
  const dLat = (lat2 - lat1) * Math.PI / 180;
  const dLng = (lng2 - lng1) * Math.PI / 180;
  const a = Math.sin(dLat / 2) ** 2 +
    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
    Math.sin(dLng / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

This two-stage approach - a cheap index-backed rectangle filter, then precise distance math on the survivors - is what makes nearest-me search fast without a dedicated spatial engine. It scales comfortably into the tens of thousands of listings per city before a real spatial index becomes worth the operational overhead.

3. Cache the map, not the API call

Rendering the actual map tiles is the one piece that legitimately needs a maps vendor. We keep that bill flat by loading the map only when a user explicitly switches to map view - the default list view needs no maps SDK at all - and by capping tile requests with viewport-based lazy loading instead of re-rendering on every scroll pixel.

4. Design for "near" being a first-class filter

  • Combine geo-radius with your other filters (price, room type, availability) in the same query, not as a separate post-filter step.
  • Default the search radius to something sensible for the city's density - 2km reads very differently in a dense metro versus a suburb.
  • Store the last confirmed location per user so repeat visits skip the permission prompt.
  • Expand the radius automatically (with a visible note) when a strict radius returns too few results - an empty page loses the visitor.

The maps bill scales with API calls, not with users. Design so distance is math you already have the numbers for, not a network request you make on every keystroke.

How do you build nearest-me search without paying per query?

Geocode addresses once at write time and store coordinates directly on each record, so search never calls an external API. Pre-filter with an indexed bounding-box query, then compute exact Haversine distance only on that narrowed set in application code. Reserve the paid maps SDK for rendering tiles on an explicit map view, not for every search request.

Share this guideLinkedInXWhatsAppFacebook
All guides

Want this built for you?

Book a free consult - we'll scope it and give you a fixed price.