Why I chose Supabase for a dating app I'm building in my spare evenings
ContenidoContents
In spare evenings I’m building a dating app. Nothing life-changing for anyone — a personal project to learn by shipping something with real users involved, which is where the real learning happens. Before writing a single line of backend code I asked Claude Code what to use, and it suggested Supabase. I knew it by reputation — “an open-source Firebase” — but reputation isn’t enough when what you’re putting there is accounts, locations and photos of real people.
So before putting anything on it, I first understood what was underneath.
What Supabase actually is
Supabase is a managed Postgres with a toolbox built around it. The core is a real, unrestricted PostgreSQL database, and around it you get what you’d otherwise have to build by hand in any app:
- Auth: email, phone or social login, with sessions and tokens already handled.
- Storage: file uploads (photos, avatars) with public or private buckets.
- Realtime: instant notification of database changes — useful for chat or for knowing who’s online right now.
- Edge Functions: your own code running on their infrastructure when you need it.
- Auto-generated API: PostgREST reads your database schema and gives you a REST API without writing a single endpoint.
The difference with Firebase, the usual comparison, isn’t a nuance. Firebase is NoSQL and proprietary; if you ever want to leave, you take a JSON dump and rebuild the relationships yourself. Supabase is standard Postgres. If I ever want to leave, I take a pg_dump and run that same database wherever I want, because it’s open source and can be self-hosted. For now I use their cloud, but it’s worth knowing the exit door isn’t bricked shut.
Why it fits this particular app
This isn’t an abstract “Postgres beats NoSQL” choice. It’s five very concrete things this app needed that Supabase already had solved.
1. Proximity search, without reinventing geometry
The heart of a dating app is “who’s near me right now.” Postgres has an extension, PostGIS, built exactly for this, and Supabase enables it with one click. You store the location as a geography type and query by radius directly in SQL:
1select id, name
2from profiles
3where st_dwithin(
4 location,
5 st_setsrid(st_makepoint(:lng, :lat), 4326)::geography,
6 5000 -- meters
7)
8order by location <-> st_setsrid(st_makepoint(:lng, :lat), 4326)::geography
9limit 20;
Computed by hand in the backend with raw latitudes and longitudes, that’s a steady source of subtle bugs (the Earth is a sphere, not a plane). Here it’s a query with a spatial index behind it, as fast as any other.
2. Row Level Security: privacy lives in the database, not in my code
This is what convinced me the most. A dating app has blocked profiles, people who don’t want to be seen by certain others, messages only two people should ever read. The usual way to handle this is scattering permission checks across every backend endpoint. And it only takes forgetting one to have a data leak.
Postgres has Row Level Security (RLS): policies evaluated inside the database itself, on every row, no matter where the query comes from. A “I only see my own profile or profiles that haven’t blocked me” policy is written once:
1create policy "view non-blocked profiles"
2on profiles for select
3using (
4 not exists (
5 select 1 from blocks
6 where blocks.blocked_id = profiles.id
7 and blocks.user_id = auth.uid()
8 )
9);
And from then on, even if I mess up somewhere in the app code, the database still won’t hand back what it shouldn’t. That’s the difference between trusting the code above to remember to check a permission, and it being physically impossible to skip.
3. Realtime, for chat and “online now”
Supabase Realtime listens to Postgres changes (it uses the logical replication the engine already ships with) and pushes them over WebSocket. For the app’s chat and for the typical green “active now” dot, that means I don’t run a separate WebSocket server or manage its own lifecycle: I subscribe to a table and that’s it.
4. Auth without writing my own session system
Email login, phone login, Google or Apple — the usual for a dating app, where asking people to “sign up with a form and confirm your email” is friction that loses users. All of that comes solved, tokens and session renewal included. Writing this by hand isn’t hard, it’s tedious and easy to leave with a hole in it.
5. Storage for photos, with signed URLs
Profile photos go in a private bucket, not a public one: nobody should be able to guess the URL of someone else’s photo. Supabase Storage generates signed URLs with an expiry, so the app requests a temporary URL each time instead of serving a permanent link that could be shared outside the app.
The fine print
None of this is free in the sense of “no cost,” only in the sense of “no bill this month”:
- The free plan pauses the project after a week of inactivity. For a project I’m poking at in the evenings, that means waking it up by hand every so often.
- RLS has to be tested for real, not assumed to work because the policy “looks right.” A badly written policy can be as permissive as having none at all, and the mistake doesn’t show up the way a backend
500would. - Realtime, being built on logical replication, has a concurrent-connection cap per project on the free tier — worth watching if this ever gets real users.
The verdict, so far
For a personal project I don’t know will go anywhere, building my own Postgres, my own authentication system and my own WebSocket server from scratch would have meant reinventing three wheels to reach the same starting point. Supabase let me start with the interesting part — the data model, the privacy policies, the app’s own logic — instead of the plumbing.
Still building it. If it ever ships, or if I trip over something worth writing about along the way, I’ll keep writing.