MongoDB is not the answer to your lack of schema discipline

5 min read

A UK office desk with a laptop showing a MongoDB JSON schema validator script in an editor

Your team picked MongoDB because "it is schemaless, so we can ship faster." That sentence has cost every UK company I have audited more than six figures. MongoDB is not the answer to your lack of schema discipline, and pretending otherwise turns a friendly document store into an unowned swamp within a year. The storage engine never cared about your schema. Your application did, and now it lives in 14 different shapes across 40 microservices. Time to fix the real problem.

The schemaless lie everyone keeps repeating

MongoDB stores BSON. BSON has field names and types. There is no universe in which that is "schemaless." What MongoDB actually gives you is the option to skip the CREATE TABLE ceremony and enforce structure later, in code, by accident. That is a footgun marketed as a feature.

Look at any Monzo outage retrospective and the pattern is identical: a field got renamed in one service, the consumer kept reading the old one, and three weeks of transactions got written with user_id, userId, and uid in the same collection. The query layer then needs three branches of $or to read it. None of this would survive ten minutes in Postgres with a NOT NULL constraint.

What "flexible" actually buys you in production

You get a schema defined by whichever developer shipped last Friday. That schema lives in three places: the Mongoose model on the Node side, the PyMongo write path, and the aggregation pipeline that joins two collections by a field that half your team spells correctly. There is no source of truth. There is only consensus, and consensus dies on bank holidays.

The real flex comes from the data. NHS Digital has spent years migrating legacy patient records off document stores that accumulated every JSON shape a contractor ever pushed. The migration cost more than building the original system on Postgres would have. That is the bill for skipping the schema discussion.

Where the discipline actually lives

Schema discipline is not a database feature. It is a contract between writers and readers, and it has to be enforced somewhere. You have three honest options, and only one of them is "do nothing in the database."

  • Postgres with a migration tool. sqitch, alembic, or dbmate gives you a reviewable diff. Adding email TEXT NOT NULL UNIQUE is a one-line PR with a reviewer.
  • MongoDB with JSON Schema validation. The server actually supports this, and almost nobody uses it.
  • Application-level validation only. This is what you have today. This is why you are here.

BBC backends run on Postgres for the parts that matter and Mongo for the parts that genuinely are document-shaped (asset metadata, editorial drafts). They do not pick Mongo to avoid migrations. They pick it because the access pattern is document-shaped. If your reason is "we cannot agree on the shape," that is a team problem, not a database problem.

Use the validation you already have, then layer on indexes

If you genuinely have a document-shaped workload, do it properly. Define the schema on the server. Add partial indexes for the fields you actually query. Stop letting Mongoose be the only thing standing between you and garbage data.

db.createCollection("users", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["email", "createdAt", "status"],
      properties: {
        email: {
          bsonType: "string",
          pattern: "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"
        },
        createdAt: { bsonType: "date" },
        status: {
          bsonType: "string",
          enum: ["active", "suspended", "pending"],
          description: "must be a known lifecycle state"
        },
        legacyUid: {
          bsonType: ["string", "null"],
          description: "deprecated, kept for one migration window"
        }
      }
    }
  },
  validationLevel: "strict",
  validationAction: "error"
});

db.users.createIndex({ email: 1 }, { unique: true });
db.users.createIndex({ status: 1, createdAt: -1 });

Now writes that omit email or pass a typo'd status get rejected at the server. No more reconciling four shapes in every query. If you cannot bring yourself to write the above, you do not have a database problem, you have a process problem, and switching to Postgres will not fix it.

The migration path when you have already made the mess

Most UK teams reading this are not greenfield. You have three million documents, half of them with a deleted boolean, the rest with a is_deleted boolean, and a couple of stragglers with a removed_at timestamp. Here is the order of operations.

First, stop the bleeding. Add the JSON Schema validator above and reject future writes that do not match. New data is clean. Old data still hurts, but at least the rot is contained. Second, write a one-off script that $renames the duplicates and backfills the missing required fields from whatever sibling collection carries the truth. Run it on a read replica, dry-run the counts, then schedule the write. Third, deprecate the legacy fields with a TTL index or a soft-delete column so the next person inherits a clean shape.

GOV.UK service teams do exactly this when consolidating onto a single GDS platform. The schema is not invented at write time; it is negotiated, validated, and enforced. The database is the enforcement layer, not a passive bucket.

What to do on Monday morning

If you are starting new: use Postgres. alembic init, write the migration, add the NOT NULL, sleep well. If you are stuck on Mongo for legitimate document reasons, add JSON Schema validation this week and write the indexes you actually query against. If you are stuck on Mongo because a manager said "NoSQL scales," you have a meeting to schedule, not a tech problem.

The point is not that MongoDB is bad. The point is that skipping schema discipline is bad, and no storage engine fixes it for you. Own the contract, enforce it at the boundary, and stop blaming the database for the mess your team is shipping.

FAQ

Is MongoDB really schemaless?

No. MongoDB stores BSON documents with named fields and types. It skips the CREATE TABLE step but the data still has a shape, and that shape is whatever the last writer decided. Without explicit validation you are running with implicit, unowned, and constantly drifting structure.

When is MongoDB the right choice over Postgres?

When the access pattern is genuinely document-shaped: nested arrays you read as a whole, deeply variable metadata like CMS drafts, or asset catalogues where the fields per item genuinely differ by type. Not when the reason is "we want to move fast without migrations."

What is the minimum schema discipline MongoDB users should adopt?

Enable JSON Schema validation with validationLevel: "strict" and validationAction: "error" on every production collection, define required fields and enums, and add unique indexes for fields you treat as identifiers. That single step eliminates most of the pain teams blame on MongoDB.