Browse documentation
MongoDB TutorialVerified with mongodb 7.5.0

Data Modeling in MongoDB

Design MongoDB documents around application access patterns and choose between embedding related data and referencing separate collections.

Model for the work your application performs

Relational design often begins by normalizing entities. MongoDB design begins with the reads and writes the application must perform. Data that is read and updated together is a strong candidate for one document, as long as the document remains bounded and ownership is clear.

Start by listing your most important operations:

  • Show an order with its line items.
  • List recent orders for one customer.
  • Update the shipping state of one order.
  • Report total sales by day.

The resulting model should make frequent operations direct while keeping uncommon work possible.

Embedding stores a related value inside its owning document. An order normally owns a snapshot of its line items, so embedding keeps the order readable with one query:

use("mongog_tutorial");
const orders = db.collection("modeling_orders");

orders.deleteMany({ tutorial: "modeling" });
orders.insertOne({
  orderNumber: "MG-1001",
  customerId: ObjectId(),
  shippingAddress: {
    city: "Istanbul",
    country: "TR",
  },
  items: [
    { sku: "KB-01", name: "Keyboard", quantity: 1, unitPrice: 89.9 },
    { sku: "PAD-02", name: "Desk pad", quantity: 2, unitPrice: 14.5 },
  ],
  status: "placed",
  placedAt: ISODate(),
  tutorial: "modeling",
});

orders.findOne({ orderNumber: "MG-1001", tutorial: "modeling" });

The address and product name are intentional snapshots. A later change to the customer's current address or the product catalog should not rewrite history for an existing order.

Reference shared or unbounded data

Referencing stores an identifier that points to a document in another collection. It is usually a better fit when the related record has an independent lifecycle, is shared by many owners, or can grow without a practical bound.

const customerId = ObjectId();

db.collection("modeling_customers").insertOne({
  _id: customerId,
  name: "Mina Kaya",
  email: "[email protected]",
});

db.collection("modeling_orders").insertOne({
  orderNumber: "MG-1002",
  customerId,
  items: [{ sku: "MOUSE-03", quantity: 1, unitPrice: 44.0 }],
  status: "placed",
  placedAt: ISODate(),
  tutorial: "modeling",
});

MongoDB does not automatically join referenced documents when you read them. Your application can issue another query or use $lookup in an aggregation pipeline when a combined view is needed.

Keep arrays bounded

An array that grows forever can eventually make a document expensive to update and approach MongoDB's document size limit. Do not embed an unlimited event log, every message in a busy channel, or all orders for a long-lived customer. Store those records separately with an indexed owner identifier.

Use a bounded recent-items array only when its maximum size is enforced. The $push operator with $slice can retain a fixed number of recent values.

Validate the important shape

Flexible schema does not mean accidental schema. Use application validation for friendly errors and collection validation for critical server-side guarantees. Add indexes that support required uniqueness and common access paths.

Expected result

The tutorial order can be read without a join, and its customerId can be used to retrieve the independent customer. This mixed approach is common: embed owned snapshots, reference shared identities.

Model cardinality and lifecycle explicitly

Ask two questions for every relationship: how many related values can exist, and who owns their lifecycle?

  • One-to-one owned settings often embed naturally.
  • One-to-few values such as order line items can embed when a practical maximum exists.
  • One-to-many records such as customer orders usually belong in their own collection with an indexed customerId.
  • Many-to-many relationships often use references on the side that makes the main access pattern direct.

Also decide whether a copied value is a live cache or a historical snapshot. An order’s product name and price are usually historical facts. A user’s current display name may instead require a synchronization policy.

Estimate document growth

MongoDB documents have a 16 MiB maximum BSON size, but staying under the limit is not the only goal. Large, frequently rewritten documents increase network cost and can create contention around one hot record.

For arrays with a fixed recent-history requirement, enforce the bound in the update itself:

orders.updateOne(
  { orderNumber: "MG-1001", tutorial: "modeling" },
  {
    $push: {
      recentEvents: {
        $each: [{ type: "viewed", at: ISODate() }],
        $position: 0,
        $slice: 20,
      },
    },
  },
);

This retains at most 20 recent events. Store a complete audit stream separately when every event must be preserved.

Review the model with real queries

Write down the filters, projections, sorts, and update paths for the highest-volume operations. Then verify that each has a direct model and an index strategy. A model that looks elegant in sample documents can still be wrong if the application repeatedly joins, scans, or rewrites large values.

Practice by modeling a support ticket with comments. Compare embedding every comment, embedding only the latest five, and storing all comments separately. Choose a model for a ticket page, a global comment moderation queue, and long-lived tickets with thousands of comments. There may be more than one valid answer, but the tradeoff should be explicit.

Common mistakes

  • Copying a relational schema one table at a time often creates unnecessary application-side joins.
  • Embedding an unbounded relationship risks oversized documents and increasingly expensive updates.
  • Duplicating data without deciding which copy is authoritative creates inconsistent updates.
  • Designing only for storage ignores the query shapes and indexes the application will need.
  • Treating every duplicated value as an error can force avoidable joins, while duplicating without an owner creates drift.
  • Checking only the 16 MiB hard limit ignores the performance cost of frequently rewriting a large document.

Continue learning

Put the model to work by inserting documents safely, then study $lookup for referenced relationships. See MongoDB's official data modeling introduction and relationship modeling patterns.