Common Modeling Patterns
Introduction
Beyond embed-vs-reference, MongoDB practitioners reuse named patterns for catalogs, time series, social feeds, and tags. This chapter walks through one-to-few, one-to-many, many-to-many, bucket, attribute, and subset patterns with mongosh examples you can adapt to Blog project and Product catalog project.
Prerequisites
One-to-Few: Embed in Parent
Use when: child count is small and stable (≤ ~20).
db.users.insertOne({
name: "Ada",
addresses: [
{ label: "home", city: "London", zip: "SW1A" },
{ label: "office", city: "London", zip: "EC1A" }
]
})Query:
db.users.find({ "addresses.label": "home" })One-to-Many: Array vs Child Collection
Pattern A — Array of subdocuments (bounded)
Good for order with few line items:
db.orders.insertOne({
orderNumber: "ORD-1001",
customerId: ObjectId("665f0123456789abcdef01234"),
items: [
{ sku: "KB-01", qty: 1, price: NumberDecimal("129.99") },
{ sku: "MS-02", qty: 2, price: NumberDecimal("29.99") }
],
total: NumberDecimal("189.97"),
createdAt: new Date()
})Pattern B — Child collection (unbounded)
order_items with orderId:
db.order_items.insertMany([
{ orderId: ObjectId("..."), sku: "KB-01", qty: 1 },
{ orderId: ObjectId("..."), sku: "MS-02", qty: 2 }
])Index { orderId: 1 } for fast find({ orderId }).
Choose B when line items or comments can grow without bound.
Many-to-Many: Array of IDs vs Junction Collection
Tags on posts — array of tag IDs
const tagDb = db.tags.insertOne({ name: "mongodb", slug: "mongodb" }).insertedId
const tagPy = db.tags.insertOne({ name: "python", slug: "python" }).insertedId
db.posts.insertOne({
title: "PyMongo Tips",
slug: "pymongo-tips",
tagIds: [tagDb, tagPy]
})
db.posts.find({ tagIds: tagDb })Junction collection (with extra fields)
When relationship has metadata (taggedAt, weight):
db.post_tags.insertOne({
postId: ObjectId("..."),
tagId: tagDb,
taggedAt: new Date()
})Query posts for a tag via junction:
const links = db.post_tags.find({ tagId: tagDb }).map((l) => l.postId)
db.posts.find({ _id: { $in: links } })Attribute Pattern (Polymorphic Products)
Different product types share name, price, but specs differ:
Query phones with 256GB:
db.products.find({
category: "phone",
"attributes.storage": "256GB"
})Alternative: attributes as array of { key, value } pairs for dynamic faceted search—trade query simplicity for flexibility.
Bucket Pattern (Time Series)
IoT or logs: bucket readings by hour/day in one document.
db.sensor_buckets.insertOne({
sensorId: "temp-01",
date: "2026-05-28",
hour: 14,
readings: [
{ t: new Date("2026-05-28T14:00:00Z"), v: 22.1 },
{ t: new Date("2026-05-28T14:05:00Z"), v: 22.3 }
],
count: 2,
avg: 22.2
})Append with $push + $inc; cap array with $slice in $push.
Benefits: fewer documents than one reading per doc; queries by bucket key.
Compare Redis streams for real-time ingest — Redis Streams vs MongoDB for durable analytics storage.
Subset Pattern (Latest N Embedded)
Store only recent comments on post; full history elsewhere.
db.posts.updateOne(
{ slug: "pymongo-tips" },
{
$push: {
recentComments: {
$each: [{ author: "Dan", body: "Thanks!", at: new Date() }],
$sort: { at: -1 },
$slice: 5
}
}
}
)$slice: 5 keeps newest five in array. Archive all comments in comments collection.
Extended Reference (URL + Metadata)
Store denormalized snippet + id for display without join:
db.posts.insertOne({
title: "Weekly Update",
author: {
id: ObjectId("665f0123456789abcdef01234"),
name: "Ada Lovelace",
avatarUrl: "/avatars/ada.png"
}
})Update author.name in all posts when user renames—or accept staleness and refresh on read from users.
Pattern Selection Table
| Pattern | Growth | Query | Update |
|---|---|---|---|
| One-to-few embed | Low | Single read | Parent rewrite |
| One-to-many array | Medium | Single read | $push / $pull |
| One-to-many collection | High | Indexed parentId | Child docs only |
| Many-to-many junction | High | Two-step or $lookup | Junction rows |
| Attribute | Varies | Field per category | Partial $set |
| Bucket | High volume | By bucket key | $push readings |
| Subset | High children | Fast homepage | Sync subset job |
FAQ
Which pattern for blog comments?
Few comments: embed. Production CMS: comments collection + optional subset on post — Blog project.
Duplicate SQL EAV table?
Attribute pattern resembles EAV—MongoDB makes it natural without extra join tables.
Sharding large buckets?
Shard key on sensorId + date — chapter 15.
Validate attribute shape?
$jsonSchema per category via partial validators — advanced; see chapter 14.
ORM for patterns?
PyMongo maps documents directly—design in code + validation — PyMongo.