database · tutorial · comparison
Mongo Today, Postgres Tomorrow? Read This First
Mongoose schemas, index discipline, and the parity verbs that keep Mongo portable. Use documents well, escape cleanly. Inside.

Documents became a swamp
Picture year one with Mongo.
No migrations.
Nested orders inside users.
Ship daily.
Year two, the swamp.
User documents with eleven optional fields, three deprecated, nobody sure.
Orders nested three levels deep and unqueryable for analytics.
Two collections holding the same email in different shapes.
Queries turn into archaeology. Which of the four email fields is canonical? Aggregations that SQL does in one line become pipeline stages written once and feared forever.
The flexibility you loved early becomes ambiguity you pay for monthly.
Teach documents with discipline. And leave the exit open.
Disciplined or swamp
| Practice | Swamp, year two | Disciplined |
|---|---|---|
| Schema | Schemaless, "flexible!" | Real schemas, required fields |
| Email fields | Several variants, unknown canon | One, unique and indexed |
| Nesting | Orders three deep in users | References and plain reads |
| Indexes | Whatever defaulted | Declared per query pattern |
| Portability | Locked to aggregations | Shared actions mirror SQL |
The swamp teaches three lessons.
Schemaless means schema-later, with interest. Every unspecified field becomes a migration question under deadline instead of at design. Schemas with required flags and defaults cost minutes up front and save quarters of archaeology.
Nesting optimizes reads and bankrupts everything else. Embedded orders load fast and analyze never. Reference by ID. Read plain data. Join in code at solo scale.
Aggregations are lock-in wearing power. A lookup labyrinth translates to no SQL engine. Shared actions keep cleverness portable: implement per engine, call uniformly.
What to do
Schema everything. Require deliberately.
Each entity gets a real schema with required fields and defaults, mirroring the columns on the other engines. A user is an email, a paid-access flag and a plan type, with the same meanings everywhere.
Schemas are documentation the database enforces. They stop the swamp at the front door.
Index per query pattern. Check with explain.
Every list the app shows needs a supporting index, declared beside the schema and reviewed with it.
Unindexed MongoDB at scale is a full-collection scan wearing a query costume. Silent at a thousand documents. Fatal at a million.
Every so often, run explain on your busiest queries and confirm they use an index.
Speak through the shared actions. Never Mongoose directly.
The app asks for "this user's orders" through one set of actions. Underneath, MongoDB returns plain objects instead of heavy document instances.
The rest of the app is engine-blind. The day another database calls, those actions get a new implementation and no feature code changes.
Swamp or disciplined
| Audit question | Swamp era | Disciplined |
|---|---|---|
| Which email field is canonical? | Nobody is sure | One, unique and indexed |
| Analytics queries | Pipeline archaeology | Plain finds, joins in code |
| Slow-query risk | Found in production | Declared and checked |
| Switching to Postgres | A rewrite of every aggregation | Reimplement the shared actions |
| A new developer | "Ask someone" | "Read the schemas" |
The cost
Discipline costs upfront thought per field.
Required?
Default?
Index?
That is minutes per schema, paid before any pain. Against swamp quarters, it is afternoons against months.
Know this first
MongoDB is not the default. SQLite is simpler and free for a single box.
Choose MongoDB for document-shaped data or an existing team.
Why BoringPush is the way to do this
MongoDB, with a way out.
BoringPush gives MongoDB real schemas and the same behavior as SQLite and Supabase. Your app code never knows which one runs.
Stay as long as it fits.
FAQ
Is MongoDB better than SQLite-first for most solos?
Rarely at the start. SQLite wins on zero setup, file speed and trivial backups at single-box scale.
MongoDB earns its place with document-shaped data such as nested configs and variable attributes, or when a team already knows it. Default to SQLite, choose MongoDB for shape-fit, and keep parity either way.
How do transactions work across engines?
Each engine has its own. MongoDB supports multi-document transactions on a replica set, SQLite has its own transaction modes and Postgres has the usual ones.
Keep cross-entity writes rare by design, and transact where atomicity truly matters. The shared actions cover the verbs, while transactions stay engine-native.
Should I use Mongoose or the native driver?
Mongoose for solos, because schemas enforce discipline. The kit standardizes on Mongoose with plain-object reads. Keep virtuals and deep populate chains minimal, because magic you cannot trace is magic you will debug at midnight.
How do I migrate messy data into disciplined schemas?
Freeze writes to the ambiguous fields, export and profile them, and write transforms that pick a canonical value. Import behind schema validation and verify counts. Do one collection at a time, on staging first, with snapshots taken beforehand.
Do text search and analytics belong in MongoDB?
Basic text indexes cover simple search such as product names and post titles. Serious search graduates to a dedicated engine later.
Analytics can read plain results into code at solo scale. Move to pipelines or a warehouse when you outgrow that, on evidence.
When is MongoDB the wrong call?
For relational-heavy domains such as ledgers, complex joins and strict integrity across entities, where SQL's guarantees do real work daily. Match the engine to your integrity needs first, and fashion never.
Keep reading
- Switch Databases With One Line (Zero Rewrites) — the exit this discipline preserves
- One Auth Flow, Three Databases, Zero Forced Logouts — users surviving flips
- SQLite vs Mongo vs Supabase: 2 Triggers End the Debate — when each engine wins
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


