Defensive Modeling: Schema Validation

A common myth is that MongoDB has no schema. While it is flexible, production applications require structure. To enforce this, MongoDB uses JSON Schema Validation at the database level.

1. The Gatekeeper: JSON Schema

You define validation rules when you create a collection (or update it). These rules act as a gatekeeper: no document can enter unless it follows the rules.

[!WARNING] Performance Cost: Validation happens on every write. While fast, extremely complex regex patterns in your schema can slow down insert speeds.

Interactive: The Validator

Test if a document passes the schema rules.

  • Name: Required String.
  • Age: Integer ≥ 18.
  • Role: Must be one of ["user", "admin", "moderator"].
  • Email: Must match regex ^.+@mongodb\.com$.
Ready to validate...

2. Defining Rules in Code

Most developers define these rules in their application migration scripts, not manually in the shell. The structure follows the standard JSON Schema draft 4 specification.

Java

import com.mongodb.client.model.ValidationOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import org.bson.Document;

// Define schema as a Document
Document jsonSchema = new Document("bsonType", "object")
    .append("required", Arrays.asList("name", "email", "age", "role"))
    .append("properties", new Document()
        .append("name", new Document()
            .append("bsonType", "string")
            .append("description", "must be a string and is required"))
        .append("age", new Document()
            .append("bsonType", "int")
            .append("minimum", 18)
            .append("description", "must be an integer >= 18"))
        .append("role", new Document()
            .append("enum", Arrays.asList("user", "admin", "moderator")))
        .append("email", new Document()
            .append("bsonType", "string")
            .append("pattern", "^.+@mongodb\\.com$")));

// Apply options
ValidationOptions valOptions = new ValidationOptions()
    .validator(new Document("$jsonSchema", jsonSchema));

CreateCollectionOptions createOptions = new CreateCollectionOptions()
    .validationOptions(valOptions);

db.createCollection("users", createOptions);

Go

import "go.mongodb.org/mongo-driver/mongo/options"

// Define the JSON Schema as BSON
jsonSchema := bson.M{
    "bsonType": "object",
    "required": []string{"name", "email", "age", "role"},
    "properties": bson.M{
        "name": bson.M{
            "bsonType": "string",
            "description": "must be a string and is required",
        },
        "age": bson.M{
            "bsonType": "int",
            "minimum": 18,
            "description": "must be an integer >= 18",
        },
        "role": bson.M{
            "enum": []string{"user", "admin", "moderator"},
        },
        "email": bson.M{
            "bsonType": "string",
            "pattern": "^.+@mongodb\\.com$",
            "description": "must match regex pattern",
        },
    },
}

validator := bson.M{"$jsonSchema": jsonSchema}
opts := options.CreateCollection().SetValidator(validator)

err := db.CreateCollection(ctx, "users", opts)

3. Validation Levels & Actions

You can control how strict the database is. This is useful when introducing validation to an existing collection with “messy” data.

Setting Option Behavior
validationLevel strict (Default) Validate all inserts and updates.
  moderate Validate inserts and updates to valid documents. Ignore updates to already-invalid documents.
validationAction error (Default) Reject the write.
  warn Allow the write but log a warning.

[!TIP] Use validationAction: "warn" when you first deploy a schema to production. This lets you monitor logs for violations without breaking the application for users.