Referencing: ObjectIds, Cardinality, and Lifecycle

Store relationships with identifiers

Referencing stores a related document's identifier rather than embedding all of its fields. In MongoDB, a reference is often an ObjectId, but it can also be another stable key, such as a UUID or a business identifier.

References are useful when related data:

  • Is large or unbounded.
  • Is shared by many parent documents.
  • Has an independent lifecycle or permissions.
  • Is frequently updated and should not be duplicated everywhere.
  • Must be queried independently.
  • Would make parent documents too large or frequently rewritten.

One-to-many example

A blog post may have many comments. If comments can grow indefinitely and need pagination, keep them in a separate collection:

// posts
{
  _id: ObjectId("65a000000000000000000030"),
  title: "Designing a MongoDB Schema",
  authorId: ObjectId("65a000000000000000000010"),
  publishedAt: ISODate("2026-02-01T10:00:00Z")
}

// comments
{
  _id: ObjectId("65a000000000000000000031"),
  postId: ObjectId("65a000000000000000000030"),
  authorId: ObjectId("65a000000000000000000011"),
  body: "The array-growth discussion helped.",
  createdAt: ISODate("2026-02-01T11:00:00Z")
}

Create an index for the common access pattern:

db.comments.createIndex({ postId: 1, createdAt: -1 });

Then retrieve a page of comments:

db.comments.find({ postId })
  .sort({ createdAt: -1, _id: -1 })
  .limit(20);

For stable pagination at scale, use a cursor based on the last seen sort values rather than relying on a large skip.

References are not automatically enforced foreign keys

A field named authorId does not make MongoDB automatically guarantee that the referenced author exists. Your application, schema design, transactions, and validation strategy must maintain referential integrity where required. Consider deletion semantics explicitly: restrict deletion, cascade with a controlled workflow, soft-delete, or preserve historical snapshots.

$lookup and application-side reads

Use $lookup when joining collections is appropriate for the query and supported by indexes and workload. Alternatively, perform two targeted queries in the application. Avoid assuming either approach is universally faster: measure realistic data volume, result size, indexes, and latency.

Specifications & Requirements
  • Reference:A field containing the identifier of another document.
  • $lookup:An aggregation stage that combines documents from another collection.
  • Referential integrity:Rules that keep relationships valid as records change.
Note

A reference is a relationship convention, not a foreign-key constraint. Define what happens when referenced records are missing, changed, or deleted.

Join comments with their post when a combined result is needed
db.comments.aggregate([
  { $match: { postId: ObjectId("65a000000000000000000030") } },
  { $sort: { createdAt: -1, _id: -1 } },
  { $limit: 20 },
  {
    $lookup: {
      from: "posts",
      localField: "postId",
      foreignField: "_id",
      as: "post"
    }
  },
  { $unwind: "$post" },
  {
    $project: {
      body: 1,
      createdAt: 1,
      "post.title": 1
    }
  }
]);
Key Takeaways
  • Use references for shared, independently managed, or unbounded data.
  • Index the foreign key and sort fields used by common child queries.
  • Plan deletion and missing-reference behavior explicitly.
Knowledge Check

Why are comments often stored separately from a post?