Skip to main content

On This Page

Draft / Scheduled Content

This article is a draft or scheduled for future publication. The content is subject to change.

Why MongoDB is Still the Wrong Choice for 99% of Projects

5 min read
Share

The “Web Scale” Delusion

In the early 2010s, MongoDB took the tech world by storm.

Supported by the infamous “MongoDB is Web Scale” meme, developers flocked to document databases. The pitch was incredibly appealing: “Ditch the constraints of SQL tables, schemas, migrations, and joins! Store your data as flexible, nested JSON documents. If you want to add a field to a record, just save it! Let your database evolve as fast as your code.”

It felt like developer liberation. No more running ALTER TABLE migrations. No more arguing about foreign keys. Just throw JSON at the database and get on with your day.

But as these applications matured, developers made a painful discovery: your data always has a schema.

By choosing a “schemaless” database, you didn’t eliminate the schema. You just moved the responsibility of enforcing that schema from the database engine (which is optimized for it) to your application code (which is terrible at it).

MongoDB remains the default database choice for many bootcamps and starters. It is almost always the wrong choice.

The Lie of “Schemaless”

The core selling point of MongoDB is that it is “schemaless.” You can write any document to any collection, regardless of its structure:

// Document 1 in "users" collection
{ "id": 1, "name": "Alice", "email": "[email protected]" }

// Document 2 in "users" collection
{ "id": 2, "name": "Bob", "contact_info": { "work_email": "[email protected]" } }

This looks flexible. But think about what happens when your application needs to read this data:

// Application code must handle every historical variation
const email = user.email || (user.contact_info && user.contact_info.work_email);

As your product evolves and your data model changes, your database ends up containing a geological record of every schema version you have ever deployed.

Because the database didn’t enforce a schema at write time, your application code must handle every single historical variation at read time.

Your codebase becomes bloated with safety checks, try-catch blocks, and type guards. If you miss a single edge case, your application crashes or displays corrupted data to the user.

A relational database solves this at the gate. If a field is required, the database rejects the write if the field is missing. If you rename a column, you run a migration that updates all existing records instantly.

The database guarantees data integrity. You can write your application code assuming the data is clean and consistent.

The Death of Relational Integrity

Most business data is relational.

  • An order belongs to a user.
  • A product has many categories.
  • A comment references a post.

In a relational database, you enforce these relationships using Foreign Keys. If you try to delete a user who has active orders, the database prevents it. If you try to create an order for a user ID that doesn’t exist, the database rejects it.

In MongoDB, there are no foreign keys. Relationships are just strings holding IDs (userId: "123").

If a user account is deleted, their orders become orphaned records in the database. If your application code has a bug or network failure during the delete process, the database is corrupted.

You end up writing background scripts to clean up orphaned records and verify data integrity—rebuilding the basic features of a relational database database engine in your own application space.

Relational Database (Guaranteed Consistency)
+-------------------------------------------------+
|  Users Table <--- [Foreign Key Constraint]      |
|                      |                          |
|  Orders Table <------+                          |
+-------------------------------------------------+
Database engine prevents orphaned records.

NoSQL Database (No Constraints)
+-------------------------------------------------+
|  Users Collection     Orders Collection         |
|   { id: 1 }            { user_id: 1 }           |
|                                                 |
|  *Delete user 1* ---> Order 1 is now orphaned!  |
+-------------------------------------------------+

The Joins Performance Tax

MongoDB advocates argue that you should nest your relations inside the document (e.g., nesting orders inside the user document).

This works for tiny, static relationships (like a user’s addresses). But it fails for unbound relationships (like a user’s orders or a product’s reviews). A document in MongoDB has a hard size limit of 16MB. If a user buys hundreds of items, you cannot nest them.

So, you must reference them by ID. And when you need to query them together, you must run MongoDB’s $lookup operator (a join).

MongoDB’s lookup is notoriously slow and resource-intensive compared to SQL’s native, index-optimized joins. Relational databases have spent 40 years optimizing join algorithms (Nested Loop, Hash Join, Merge Join). MongoDB’s lookup is a secondary, bolted-on feature that doesn’t scale well under high concurrency.

The True Web Scale Solution: PostgreSQL

If you genuinely need document flexibility—if you have dynamic, user-defined fields or unstructured payloads—you still don’t need MongoDB.

PostgreSQL has built-in JSONB support.

You can define a table with structured columns for your core, relational data (like IDs, emails, and timestamps) and a JSONB column for your dynamic data:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    metadata JSONB -- Dynamic, schemaless fields
);

You can query, index, and modify keys inside the JSONB column using SQL:

-- Query users where metadata ->> 'role' is 'admin'
SELECT * FROM users WHERE metadata->>'role' = 'admin';

With PostgreSQL, you get the best of both worlds:

  • Statically typed, relational integrity for your core database.
  • High-performance, indexed JSON documents for your dynamic data.
  • Full ACID transactional guarantees.

Default to SQL

Unless you are building a telemetry logging system that processes gigabytes of write-only events per second (where Cassandra or a time-series DB makes sense), or you have a highly specialized graph query requirement, choose a relational database.

Specifically, choose PostgreSQL.

It is the most reliable, feature-rich, and optimized general-purpose database in existence. It will keep your data clean, your queries fast, and your application simple.

Related Content