Skip to content
all journals
  • PostgreSQL
  • MongoDB
  • Databases
  • Opinion

Postgres until proven otherwise

I run both MongoDB and PostgreSQL in production. My default flipped years ago, and the reason isn't performance — it's that schemas are promises, and Postgres makes you keep them.

Every project starts with the same fork in the road, and I've taken both paths enough times to have a default: Postgres, until the data proves otherwise. Not because MongoDB is bad — it isn't, and I run it happily in production — but because of what each database does to your future self.

Schemas are promises

A schema is not bureaucracy; it is a promise about what shape the data will be when you read it back. The question is never whether your data has a schema — it always does — but who enforces it.

  • In Postgres, the database enforces it. Bad writes fail loudly, today.
  • In Mongo, your application enforces it. Bad writes succeed silently, and fail three months later in a report nobody can explain.

Document validation in Mongo narrows this gap, and libraries like Zod or Mongoose narrow it further. But an enforcement layer you must remember to apply is a different guarantee than one you cannot forget.

Where Mongo genuinely wins

The honest list, from production experience — not the marketing one:

  1. Documents that are truly documents. CMS page trees, form builders, event payloads — data that arrives and leaves as one nested blob, where joining was never the point.
  2. Shapes you don't control. Webhook payloads and third-party syncs, where the upstream changes without asking you.
  3. Horizontal scale as a first-class citizen — if you concretely know you're heading past what one big Postgres box serves. Most of us aren't.

Meanwhile jsonb quietly ended the old dichotomy: my default stack is relational tables for the entities that relate — users, orders, permissions — with a jsonb column where a document honestly is a document. One database, both models, real transactions.

Relational for the data you promise, jsonb for the data you tolerate.

The tell

Here's the test I apply to any "which database?" debate: describe your data out loud. If the sentence contains "belongs to," "references," or "the total across" — that's a relational sentence, and fighting it in a document store means reimplementing joins in application code, badly, forever. If the sentence is "we store what we receive and show it back" — that's a document sentence, and normalizing it into fifteen tables is ceremony.