Why I Chose PostGIS Over an ORM for Bean Remote

Why I Chose PostGIS Over an ORM for Bean Remote

When I built Bean Remote - a mobile app that helps remote workers find great cafes to work from - I had to solve a core technical challenge:
How do you fetch a large set of spatial data (cafes) and display it instantly on a map, filtered by location or screen boundaries?

You might think any ORM like Prisma, Sequelize, or TypeORM could handle that. But after testing and research, I made a conscious choice to go directly with PostGIS - the spatial extension for PostgreSQL.

Here’s why I did it, what I learned, and how it shaped the performance of my app.


🧭 The Use Case: Geo-filtered Cafe Listings

Bean Remote uses:

  • React Native + Expo on the frontend
  • Node.js + Express on the backend
  • PostgreSQL + PostGIS for data and geospatial queries

I wanted users to:

  • See all nearby cafes within a certain radius
  • Filter cafes within the current map screen bounds
  • Eventually support search by travel time or availability

ORMs typically abstract away SQL complexity, but in this case… abstraction became a bottleneck.


🚀 Why PostGIS Is Built for This

1. Powerful and Fast Spatial Indexing

I store cafe and user locations as:

location GEOGRAPHY(Point, 4326)

And I apply a GIST index for fast lookups:

CREATE INDEX idx_cafes_location ON cafes USING GIST (location);

With PostGIS, I can now use ultra-efficient spatial queries like:

SELECT * FROM cafes
WHERE ST_DWithin(
  location,
  ST_SetSRID(ST_MakePoint(:lng, :lat), 4326),
  :radius
);

This will instantly return all cafes within X meters of the user - using the index.

Fast
Accurate
No post-filtering required


2. ORMs Fall Short on Native Spatial Operations

ORMs like Prisma or Sequelize do support raw SQL, but their native support for spatial features is limited or non-performant.

Example: Prisma

Prisma recently introduced spatial support via raw SQL extensions, but:

  • No built-in support for ST_DWithin, ST_Intersects, or ST_MakeEnvelope
  • No automatic index hints
  • Joins and spatial operations often need to be written manually anyway

ORM layers often rely on application-level filtering, especially for complex geometry comparisons - a major performance bottleneck on mobile.


3. Real-World Benchmarks Back It Up

In a test run with 10,000+ location points:

OperationORM (Sequelize)PostGIS
Nearby search (2km)200–300ms~15–30ms
Bounding box query300ms+~20ms
Result accuracyFiltered manuallyHandled by DB

Source: GIS StackExchange Discussion
Also see: PostGIS Index Performance Guide


✨ Simplicity and Power in SQL

I also use PostGIS for things like:

  • Ordering results by distance (ST_Distance)
  • Screen-based bounding box filtering using ST_MakeEnvelope(...)
  • Filtering only verified cafes in the same query
SELECT * FROM cafes
WHERE is_verified = true
  AND ST_Within(
    location,
    ST_MakeEnvelope(:west, :south, :east, :north, 4326)
  );

This would be extremely difficult (or messy) to express in most ORMs.


🧱 My Cafe & User Tables (with Indexing)

CREATE TABLE cafes (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255),
  location GEOGRAPHY(Point, 4326),
  ...
);
CREATE INDEX idx_cafes_location ON cafes USING GIST (location);
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  location GEOGRAPHY(Point, 4326),
  ...
);
CREATE INDEX idx_users_location ON users USING GIST (location);

These indexes are the secret sauce - enabling real-time map updates as users pan or zoom.


⚖️ ORM vs PostGIS: What to Choose?

FeatureORMPostGIS
Simple CRUD
Built-in spatial support❌ Limited✅ Full
Index optimization❌ Manual✅ Native GIST, SP-GiST, BRIN
Advanced geo queries❌ Hard to express✅ Easy + fast
Ideal for real-time maps❌ No✅ Yes

🧩 Final Thoughts

For most apps, ORMs are great. But for geolocation-heavy apps - like map-based mobile apps, delivery platforms, ride-sharing, or proximity search tools:

➡️ PostGIS gives you the control, speed, and spatial power you need.
You skip the abstraction, but you gain reliability, precision, and scalability.

Bean Remote wouldn’t be as fast or as clean without it.


🔗 References