Postgres is the only NoSQL database you need. If your team is still spinning up MongoDB clusters for "flexible schemas" in 2026, you are paying Atlas bills to recreate the relational features Postgres shipped between 2014 and 2024. MongoDB is failing your business because it forces you to rebuild joins, transactions, and aggregation pipelines that every Postgres install already has, while charging you for the privilege of running it as a managed service. The data is on your side: most "schema-less" UK shops I've audited are quietly writing the same five documents and treating the collection like a SQL table anyway.
The classic Mongo pitch was "no schema, no migrations, ship faster." That's a marketing pitch, not engineering. The moment your service is live, you have a schema - it's just enforced by application code, bug-prone, and undocumented. We saw this firsthand at a fintech I worked with in Bristol: two microservices wrote to the same collection, one wrote {user_id: 837}, the other wrote {userId: "837"}, and a quarter of invoices went to the wrong people. Postgres would have rejected one of them at insert time. Mongo just shrugged.
Then there's the cost. MongoDB Atlas on AWS eu-west-2 starts around £80/month for an M10 and climbs fast once you add replica sets, backups and BI connectors. A comparable Postgres on RDS or Supabase runs £30-60 for the same workload. Multiply that by ten collections and you've just hired a contractor for the year. There is a reason HMRC's published architecture guidance leans on Postgres for new services, and it's not because they're allergic to JSON.
Postgres added jsonb in version 9.4. It's a binary, indexable, queryable JSON type. You get document storage, GIN indexes on keys, @> containment operators, and full text search, all inside a transactional engine that also does joins. You don't pick a database per data shape; you pick the column type.
-- A real "documents" table from a UK payments platform
CREATE TABLE payment_events (
id bigserial PRIMARY KEY,
account_id uuid NOT NULL REFERENCES accounts(id),
payload jsonb NOT NULL,
created_at timestamptz DEFAULT now()
);
-- Index the bits you actually query
CREATE INDEX idx_payment_events_provider
ON payment_events USING gin (payload jsonb_path_ops);
-- Find every Stripe refund over £50 in the last 30 days
SELECT payload->>'id' AS event_id,
(payload->>'amount')::numeric / 100 AS amount_gbp
FROM payment_events
WHERE payload @> '{"type":"refund","provider":"stripe"}'
AND created_at > now() - interval '30 days'
AND (payload->>'amount')::numeric > 5000;
That single query replaces a Mongo aggregation pipeline, a map-reduce, and three stack overflow tabs. It's also ACID, which brings us to the next trap.
MongoDB only got multi-document ACID transactions in 4.0 (2018), and they still cap at 60 minutes on Atlas before throughput drops. Single-document writes were atomic before then, but the second you "embed everything" to avoid joins, you've rebuilt normalisation badly and your 16MB document limit starts killing you. The GOV.UK platform team moved several services off Mongo precisely because of these limits. Postgres transactions have been correct since the 90s.
Here's the pattern I keep seeing in post-mortems:
The Monzo playbook is the one to copy: dual-write, backfill, swap reads, retire Mongo. Concretely:
outbox writes in your service so every Mongo mutation also lands in a postgres_events table.mongoexport piped through a small Go or Python script that maps docs into normalised tables.Most teams I know finished this in under a quarter. The people who say it takes six months are usually the ones who have never actually tried.
I'm not a zealot. If you genuinely need horizontal write scaling past what a single Postgres primary can handle and you don't need joins or transactions across shards, there are cases. Pure time-series ingestion at 200k writes/sec, raw clickstream buffers that get batch-loaded into a warehouse, or teams with hard Mongo expertise - fine, keep it. But that's not 90% of UK SaaS. That's 90% of UK SaaS reinventing Postgres badly on a vendor's invoice.
Postgres is the only NoSQL database you need because it gives you documents, relations, full text, vectors, and queues under one transactional roof. Your business fails when you pay twice - once to Mongo for "flexibility," then again to your engineers to reinvent the relational features Postgres already has. Ship JSONB, keep the joins.
Functionally, yes for most workloads. With jsonb, GIN indexes and the @> containment operator you get document storage and rich querying. You also get SQL joins, transactions and mature tooling - so it's a superset of what most teams use Mongo for.
If you have proven write throughput over a single primary's capacity, no cross-collection joins, and a team fluent in Mongo aggregation. Outside of those constraints - very large analytics pipelines, raw ingest buffers, or pre-existing Mongo shops - Postgres is the better default.
Use the dual-write + backfill pattern: write to both, backfill history with a migration script, flip reads behind a feature flag, then cut over and decommission the Mongo cluster. Most UK teams I've watched do this complete it inside one quarter.