PostgreSQL vs MongoDB
Relational PostgreSQL versus document MongoDB: data model fit, ACID guarantees, scaling, performance, and which database wins for modern application workloads.
Open-source relational database running on your own server, maturing since 1986
Managed, open-source backend platform built on real PostgreSQL
This decision has no single right answer. If you're a small team and speed is the priority, Supabase is the right call: auth, storage, realtime, and a pooler come ready-made. With a predictable load, a data-residency constraint (Turkey isn't among the 17 regions), or cost that compounds with user count, self-hosted PostgreSQL wins. One warning: the official docs state that managed backup/PITR is disabled when you self-host — with control you inherit the restore drill. Having a backup isn't the criterion; being able to restore is.
| Category | Self-hosted PostgreSQL | Supabase |
|---|---|---|
| Performance | 8/10 | 8/10 |
| Ease of Learning | 5/10 | 8/10 |
| Ecosystem | 9/10 | 8/10 |
| Community | 9/10 | 9/10 |
| Job Market | 8/10 | 7/10 |
| Future-Proof | 8/10 | 8/10 |
# Self-hosted PostgreSQL - WAL archiving + PITR setup (postgresql.conf)
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /var/backups/pg_wal_archive/%f && cp %p /var/backups/pg_wal_archive/%f'
max_wal_senders = 3
# Take a base backup (pg_basebackup)
pg_basebackup -D /var/backups/base -Ft -z -P -U replicator -h localhost
# PITR: recover to a specific point in time (recovery.signal + postgresql.conf)
# 1) Extract the base backup into the restore directory
# 2) Add the following lines to postgresql.conf, create the recovery.signal file
restore_command = 'cp /var/backups/pg_wal_archive/%f %p'
recovery_target_time = '2026-09-23 09:00:00+03'
# pg_hba.conf - connections only from the application server
host appdb app_user 10.0.0.5/32 scram-sha-256
-- SQL (psql): Extension setup (full freedom - specific to self-hosting)
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS pg_cron;
# Connection pooling (PgBouncer, pgbouncer.ini)
[databases]
appdb = host=127.0.0.1 port=5432 dbname=appdb
[pgbouncer]
pool_mode = transaction
max_client_conn = 500
default_pool_size = 25-- SQL (Supabase SQL Editor): 1) Create the table and enable RLS (the Data API requires this)
create table public.notes (
id uuid default gen_random_uuid() primary key,
user_id uuid references auth.users not null,
content text not null,
created_at timestamptz default now()
);
alter table public.notes enable row level security;
create policy "kullanicilar kendi notlarini okur"
on public.notes for select
using (auth.uid() = user_id);
create policy "kullanicilar kendi notlarini yazar"
on public.notes for insert
with check (auth.uid() = user_id);
// 2) Query with supabase-js (RLS is applied automatically)
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
)
const { data: notes, error } = await supabase
.from('notes')
.select('id, content, created_at')
.order('created_at', { ascending: false })
// 3) Postgres client connection strings (official "Endpoints and IP versions" table)
// Direct connection (persistent backend, pg_dump, migration):
// postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
// Dedicated pooler (transaction mode only, paid plans):
// postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:6543/postgres
// Building the string by hand: copy it from the Dashboard > Connect screen (the shared pooler host can't be derived from the region).This decision has no single right answer. If you're a small team and speed is the priority, Supabase is the right call: auth, storage, realtime, and a pooler come ready-made. With a predictable load, a data-residency constraint (Turkey isn't among the 17 regions), or cost that compounds with user count, self-hosted PostgreSQL wins. One warning: the official docs state that managed backup/PITR is disabled when you self-host — with control you inherit the restore drill. Having a backup isn't the criterion; being able to restore is.
Get Free ConsultationSince Supabase uses standard PostgreSQL, you can export with `pg_dump`/`pg_dumpall` or with native Postgres replication; the official docs walk through cross-project migration with a Node.js script example (including auth/storage). Rebuilding Supabase-specific services like Auth, Storage, and Realtime on the self-hosted side (by running the official self-host Docker Compose stack) is a separate job — moving data and reaching service parity are different steps.