MongoDB - Relationships
MongoDB - Data Modeling for Relationships
Section titled “MongoDB - Data Modeling for Relationships”In MongoDB, relationships signify how different documents are logically connected. These connections can be modeled using two primary strategies: Embedding (denormalization) and Referencing (normalization). The choice of strategy depends on your application’s data access patterns, and it can model one-to-one (1:1), one-to-many (1:N), and many-to-many (N:N) relationships.
Let’s explore these concepts using a common scenario: a user who has multiple addresses. This represents a one-to-many (1:N) relationship.
A modern user document might look like this:
// users collection{ "_id": new ObjectId(), "name": "Alex Doe", "email": "alex.doe@example.com", "createdAt": new ISODate()}And an associated address document:
// addresses collection (for referenced model){ "_id": new ObjectId(), "street": "123 Main St", "city": "Anytown", "state": "CA", "zip": "12345"}Strategy 1: Modeling with Embedded Documents
Section titled “Strategy 1: Modeling with Embedded Documents”In the embedded approach, you place the related data directly inside the parent document. For our user and addresses, we would embed an array of address sub-documents within the user document.
// A user with embedded addressesdb.users.insertOne({ "_id": new ObjectId(), "name": "Alex Doe", "email": "alex.doe@example.com", "createdAt": new ISODate(), "addresses": [ { "type": "Home", "street": "123 Main St", "city": "Anytown", "state": "CA", "zip": "12345" }, { "type": "Work", "street": "456 Business Ave", "city": "Metropolis", "state": "NY", "zip": "54321" } ]});This approach consolidates all related data into a single document, offering significant performance benefits for read operations. The entire user profile, including all addresses, can be fetched with a single query:
// Retrieve a user and their addresses in one godb.users.findOne({ "email": "alex.doe@example.com" });Pros and Cons of Embedding
Section titled “Pros and Cons of Embedding”Pros: Faster reads (no database joins needed), data locality, and atomic updates for the entire document. Cons: Can lead to large documents (max 16MB BSON document size), data duplication if the embedded information is also needed elsewhere, and potentially slower writes if the embedded arrays grow very large.
Strategy 2: Modeling with Document References
Section titled “Strategy 2: Modeling with Document References”The referencing (or normalized) approach keeps related data in separate collections. The parent document stores references—typically the _id values—to the related documents.
// User document referencing address IDsdb.users.insertOne({ "_id": new ObjectId(), "name": "Alex Doe", "email": "alex.doe@example.com", "createdAt": new ISODate(), "address_ids": [ new ObjectId("615cba23...1"), // ID of the first address document new ObjectId("615cba23...2") // ID of the second address document ]});
// Address documents in a separate collectiondb.addresses.insertMany([ { "_id": new ObjectId("615cba23...1"), "street": "123 Main St", ... }, { "_id": new ObjectId("615cba23...2"), "street": "456 Business Ave", ... }]);To retrieve the user’s addresses, you would use the $lookup aggregation stage, which performs a left outer join to the other collection. This is the modern, efficient way to handle references.
// Using $lookup to join users and addressesdb.users.aggregate([ { $match: { "email": "alex.doe@example.com" } // Find the user }, { $lookup: { from: "addresses", // The collection to join with localField: "address_ids", // Field from the input documents (users) foreignField: "_id", // Field from the documents of the 'from' collection as: "address_info" // The new array field to add to the input documents } }]).pretty();Pros and Cons of Referencing
Section titled “Pros and Cons of Referencing”Pros: Avoids data duplication, keeps documents smaller, and is better when related data is frequently updated or accessed on its own. It’s more flexible for complex N:N relationships.
Cons: Requires an additional $lookup operation, which can be slower than reading a single embedded document. Retrieving a complete entity requires multiple collection accesses.
Best Practice: Choosing the Right Model
Section titled “Best Practice: Choosing the Right Model”The choice is not always binary. A hybrid approach is often best. Use embedding for relationships that are contained, have a clear parent-child dynamic, and are primarily accessed together (e.g., comments on a blog post). Use referencing when the related data is large, accessed independently, or part of a many-to-many relationship (e.g., products and categories).