Saarza published Why We Migrated from MongoDB to PostgreSQL on 20 December 2025. This is their company blog, not a MongoDB or PostgreSQL vendor page.
They used MongoDB to stand up SaaS prototypes. Documents went in as JSON. There was no schema to negotiate first. As those products grew, the missing schema became the bug.
If you are looking at the same route, start with the MongoDB → PostgreSQL playbook. Saarza’s post is a why, not a dump-and-restore. The playbook is the copy, the ID map, and the cutover they never describe.
Why they left
Their SaaS data was relational. Users belong to teams. Teams own projects. Projects have invoices.
MongoDB does not enforce those links for you. You write the checks in application code. Miss one path and you get an orphan. Their example is an invoice that still points at a team you already deleted.
They call that “Schemaless” becoming a nightmare. They do not publish how many orphans they found, which product hit it first, or a ticket count. The shape of the graph is the reason they give.
What they moved to
The destination is PostgreSQL plus Drizzle. Drizzle is a TypeScript ORM that stays close to SQL. You declare tables in TypeScript. It can emit foreign keys.
PostgreSQL holds the rule. Foreign keys reject a delete, or cascade it, before the application has a chance to leave a dangling invoice. Their example uses ON DELETE CASCADE on projects.team_id:
ALTER TABLE projects
ADD CONSTRAINT fk_team
FOREIGN KEY (team_id) REFERENCES teams (id)
ON DELETE CASCADE;
CASCADE is one choice. Restrict is the other one they name. Restrict refuses the delete until you deal with the children. Cascade deletes the children with the parent. They do not say which rule they shipped on invoices. The point they make is that the database now asks the question, instead of a missed if in a service.
The other gain they claim is type shape. With TypeScript and SQL, they know what an object looks like. They stopped writing user?.address?.city guards on every read. That is their wording. They do not show the old Mongoose (or other) schema, so I am not inventing one.
They close with one line: stability is worth the extra setup time. Extra setup is the cost they admit.
What they did not publish
There is no mongoexport, no ID map, no dual-run, and no cutover window. mongoexport is the usual logical dump, and MongoDB is explicit that it is not a backup tool. BSON types can change when you relax them into JSON. None of that appears in the Saarza post.
They also do not publish a row count, a downtime number, or a database bill. I am not filling those in.
The same day they posted Why We Chose Next.js 15 & Drizzle ORM for Enterprise SaaS. That piece is a different move. They left Prisma for Drizzle because Prisma’s Rust query engine slowed serverless cold starts. They stated query times went from about 300 ms to about 50 ms. That number is Prisma versus Drizzle, not MongoDB versus PostgreSQL. Do not paste it into a database-migration slide.
The stack post also says they run Next.js 15 App Router and like Server Actions. Useful for who they are. It is not a Mongo export plan.
What they gained
Integrity moved from application code into PostgreSQL. A bad delete can fail at the constraint instead of creating an orphan invoice.
The TypeScript schema is now the documented shape of a user, a team, a project. Optional chaining on nested address fields was the example they wanted to stop writing.
They accepted a slower setup in exchange for that. They do not claim faster queries, cheaper hosting, or a shorter on-call rotation on the MongoDB post.
What I would copy
Write the graph down before you pick a target schema. Users, teams, projects, invoices is already a foreign-key design. If that is your product, MongoDB is asking you to reimplement ON DELETE in every service.
Decide cascade versus restrict per child table. Cascade is convenient and easy to regret on invoices. Restrict is louder and usually safer until you have a deletion policy.
Do not treat their post as a migration runbook. You still need a native Mongo backup, a versioned PostgreSQL schema, a mapped _id, and a write freeze. The playbook lists those. Saarza never published that work.
If you also change ORMs in the same quarter, keep the two diffs apart. Prisma to Drizzle can move latency. MongoDB to PostgreSQL is about constraints. Keep the 300 ms number on the ORM slide.
Run the MongoDB → PostgreSQL playbook. Use Saarza’s post for the orphaned-invoice reason to put foreign keys in the database.