Finding Nearby Stores with FaunaDB: A Practical Guide to Geospatial Indexes
Learn how to use FaunaDB’s built‑in geospatial indexes to build fast, cost‑effective location‑based search. Step‑by‑step examples, performance trade‑offs, and best‑practice tips are included.
06 Aug 2025, 09:56 UTC

Problem: Quickly find stores near a user’s current location
Modern mobile and web apps often need to show the “closest X stores” to a user’s current GPS coordinates. The naive approach—fetch all stores and filter client‑side—is slow, expensive, and impossible at scale. A database that can natively understand latitude/longitude and return only the relevant subset is essential.
Thesis: FaunaDB’s built‑in geospatial indexes let you store, query, and rank by distance without leaving the database.
FaunaDB supports the geo index type, a first‑class data type that stores a Point (latitude/longitude) and exposes powerful predicates such as Within and Distance. Because the index is replicated and kept in memory, reads are fast and consistent with the latest writes.
1. Define a Geo Index
Assume you have a Store collection where each document contains a location field of type Point:
CreateCollection({ name: "Store" })
CreateDocument({ collection: "Store", data: { name: "Main St. Café", location: { type: "Point", coordinates: [ -122.4194, 37.7749 ] } })
Next, create the index:
CreateIndex({
name: "stores_by_location",
source: Collection("Store"),
type: "geo",
terms: [],
values: [ { field: ["ref"] } ],
geo: { field: ["location"] }
})
Key points:
type: "geo"tells Fauna that the index will support spatial predicates.- The
geoproperty points to the field that holds thePoint. - Projection (
values) can be narrowed to return only refs or other fields to reduce bandwidth.
2. Insert Sample Stores
Populate the collection with a handful of stores in different cities:
CreateDocument({ collection: "Store", data: { name: "Downtown Deli", location: { type: "Point", coordinates: [ -122.3321, 47.6062 ] } } })
CreateDocument({ collection: "Store", data: { name: "Beachside Bakery", location: { type: "Point", coordinates: [ -118.4912, 34.0195 ] } } })
In a real app, you would bulk‑import or stream these records from a CSV or external API.
3. Query Within a Radius
To find all stores within 5 km of a user’s coordinates, use the Within predicate against the geo index:
Let(
{ user: { lat: 37.7749, lon: -122.4194 } },
Within(
Ref("index/stores_by_location"),
{ type: "Point", coordinates: [ user.lon, user.lat ] },
{ radius: 5000 } // radius in meters
)
)
The query returns a list of references to Store documents that fall inside the circle. Because the index is geo‑aware, Fauna only scans the relevant spatial bucket, not the entire collection.
4. Rank by Distance
Often you want the nearest store first. Combine Within with Distance and OrderBy:
Let(
{ user: { lat: 37.7749, lon: -122.4194 } },
OrderBy(
Asc(
Distance(
Ref("index/stores_by_location"),
{ type: "Point", coordinates: [ user.lon, user.lat ] }
)
),
Within(
Ref("index/stores_by_location"),
{ type: "Point", coordinates: [ user.lon, user.lat ] },
{ radius: 5000 }
)
)
)
Resulting rows will be sorted from closest to farthest. If you need the exact numeric distance, project it in the values clause or fetch the full document after the query.
5. Limit and Cost Considerations
Each query incurs a fee proportional to the number of results returned. For a large city with thousands of stores, a 5 km radius could return many rows. Use Limit to cap the result set and keep costs predictable:
Let(
{ user: { lat: 37.7749, lon: -122.4194 } },
Limit(
10,
OrderBy(
Asc(
Distance(
Ref("index/stores_by_location"),
{ type: "Point", coordinates: [ user.lon, user.lat ] }
)
),
Within(
Ref("index/stores_by_location"),
{ type: "Point", coordinates: [ user.lon, user.lat ] },
{ radius: 5000 }
)
)
)
)
Use a bounding box (e.g., a smaller Within radius or a WithinPolygon) to pre‑filter before computing exact distances, which reduces the cost of the spherical calculation.
Trade‑offs and Limitations
- Index Size: Each geo index consumes additional storage and replication bandwidth. For millions of points, the index can become large.
- Missing GIS Functions: Fauna does not expose geohashes, clustering, or advanced spatial analytics. If you need those, pre‑compute the values in your application layer.
- Free Tier Limits: The free tier caps the number of indexes. Adding a geo index may push you over the limit, requiring a plan upgrade.
- Latency: While reads are typically <50 ms, the cost grows linearly with the number of returned rows. Use
LimitandProjectionto keep queries lean.
Actionable Next Steps
- Create a
geoindex on your location field. - Populate the index with a representative sample to gauge size and performance.
- Implement
Within+Distancequeries in your API, addingLimitand optional bounding boxes. - Monitor query cost and latency in the Fauna console; adjust projections or add secondary indexes if needed.
- If you require advanced GIS features, consider storing pre‑computed geohashes or clustering data in a separate field and indexing that.
With FaunaDB’s geospatial indexes, you can deliver a responsive “nearby stores” experience while keeping your infrastructure simple and cost‑effective.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.