You Don't Need MongoDB for "Flexible" Data. You Need One Column Type.

Product catalogs, form submissions, webhook payloads — a lot of real data genuinely doesn't want a fixed schema. The usual response is reaching for MongoDB purely for that flexibility, which means a second connection pool, a second backup strategy, and a second place your data can quietly drift out of sync with the relational tables that still describe your users and orders.
jsonb solves the actual problem — flexible structure — without giving up the database you already trust for everything else.
Store anything
One table, one jsonb column, one GIN index:
CREATE TABLE products (
id bigserial PRIMARY KEY,
name text NOT NULL,
attributes jsonb NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_products_attributes
ON products USING GIN (attributes);Use jsonb, not json. The binary form is stored decomposed at write time instead of as text re-parsed on every read — that's the difference between something you can actually index and something you can only display.
Query it like you mean it
The @> containment operator checks whether one JSON document contains all the keys and values of another:
SELECT name FROM products
WHERE attributes @> '{"color": "red", "size": "M"}';The GIN index makes this fast the same way a book's index works: it maps individual keys and values straight to the rows that contain them, instead of scanning every row to check.
The part MongoDB genuinely can't do as cleanly
A document-style attribute joined directly against relational order history — one statement, one transaction:
SELECT p.name, o.order_date
FROM products p
JOIN order_items oi ON oi.product_id = p.id
JOIN orders o ON o.id = oi.order_id
WHERE p.attributes->'specs'->>'material' = 'leather';Try that across two databases and you're writing application-layer stitching code — and hoping nothing changes between the two calls.
Why this beats standing up a document database
- ▹One transaction, not two systems — the "document" and the relational rows referencing it commit or roll back together.
- ▹No sync jobs, because there's no second database to keep consistent.
- ▹You can still join it, which is the thing you actually lose by going full-document.
Where MongoDB still wins
Genuinely schema-less workloads at a write scale where horizontal partitioning across many nodes is a day-one requirement, or built-in multi-region active-active replication out of the box. Most projects reaching for Mongo on day one aren't actually at that scale yet.
Field Manual Series · Every recipe tested
This is one of eight infrastructure swaps.
Just Use Postgres is a 24-page field manual on replacing MongoDB, Redis, Elasticsearch, Pinecone, and more with the database you're probably already running. Every recipe in it — including this one — was run against a live Postgres instance before it went in the book.
One column type vs. a second database to back up, patch, and keep consistent — the math writes itself.
Up next in the series
Replace InfluxDB With Partitioning + BRIN Indexes
See the full series →