The Goal
Build a nearby search that is easy to explain.
For most products, that means: store a point, index it, search within a radius, then sort by distance.
Create The Table
create extension if not exists postgis;
create table places (
id bigserial primary key,
account_id bigint not null,
name text not null,
location geography(point, 4326) not null,
is_active boolean not null default true
);
create index places_location_gix
on places using gist (location);
Use geography(point, 4326) when you are storing longitude and latitude and want distances in meters.
Insert A Point
st_makepoint takes longitude first, then latitude.
insert into places (account_id, name, location)
values (
1,
'Empire State Building',
st_makepoint(-73.9857, 40.7484)::geography
);
That longitude-first order is easy to get wrong.
Query Nearby Places
select
id,
name,
st_distance(
location,
st_makepoint(-73.9857, 40.7484)::geography
) as meters_away
from places
where account_id = 1
and is_active
and st_dwithin(
location,
st_makepoint(-73.9857, 40.7484)::geography,
2000
)
order by meters_away
limit 20;
The radius is explicit: 2000 meters.
Product Decisions
Do not let the database decide what nearby means.
Pick the product rule:
- Within 2 km.
- Same delivery zone.
- Closest active provider.
- Inside a boundary.
- Distance plus rating or inventory.
Once the rule is clear, the SQL is easier to write and test.
Common Mistakes
- Swapping latitude and longitude.
- Forgetting the spatial index.
- Searching with a huge radius and wondering why it is slow.
- Ranking only by distance when availability matters more.
- Moving location data away from the product data too early.
When To Move On
PostGIS can take you far. Move beyond this setup when you need routing, tile generation, heavy GIS processing, or a map stack owned by a separate team.
For nearby search in an app, keep it in Postgres until there is a real reason not to.