Blog Schema in MongoDB
Design a blog platform schema in MongoDB with this hands-on tutorial. Learn document modeling, collections, and relationships for posts and comments.
Focus: design a schema for a blog platform in mongodb
Designing a schema for a blog platform in MongoDB feels deceptively simple — you create a posts collection, add some fields, and call it done. But once you add comments, tags, authors, and drafts, you’ll hit a wall of awkward queries, bloated documents, or hard-to-maintain denormalization. This lesson walks you through a practical, battle-tested approach to modeling a blog in MongoDB, covering document structure, relationships, and indexing, so you can build a schema that’s fast, flexible, and ready for production.
The problem this lesson solves
You’re building a blog platform — posts, users, comments, tags. In a relational database, you’d normalize into tables: users, posts, comments, post_tags. With MongoDB, you have no schema constraints and no JOINs, which is both liberating and dangerous. The first instinct is to dump everything into one document, but you’ll soon find that embedding every comment (imagine 10,000 comments on a viral post) makes your documents huge and your updates painful. On the flip side, over-referencing everything creates the “N+1” query problem and slow reads. The core problem: how to structure your documents and collections so that your reads are fast, your writes are manageable, and your request patterns are supported without building a SQL-shaped object in a document database.
This lesson solves that by giving you a mental model for embedding vs. referencing, a step-by-step process to design your schema, and a concrete walkthrough for a blog platform. You’ll walk away knowing exactly how to determine what lives in a document and what lives in a separate collection.
Core concept / mental model
Think of MongoDB documents as JSON objects that can nest other objects and arrays. Your goal is to design a schema that matches your application’s data access patterns — not to echo a SQL table design. You have two main tools:
- Embedding: copy related data inside a parent document (e.g., embed a few tags as an array of strings).
- Referencing: store the
_idof a related document (e.g., a comment document that references the post_id).
A helpful analogy: embedding is like putting a photo album inside your memory box; referencing is like keeping a sticky note that says “see box #7.” If you need the photo often and always together, embed it. If you need to manage it independently or it can grow unbounded, reference it.
Key definitions:
- Collection: a group of documents — like a table in SQL.
- Document: a single record — like a row, but flexible.
- Embedded document: a subdocument inside a parent document.
- Reference: an
_idvalue that links to another document in a collection.
Rules of thumb:
- Embed when the data is read together, has a bounded size, and is not accessed independently.
- Reference when the related data is unbounded (comments, many-to-many tags), or when you need atomic updates on the child without rewriting the parent.
- Design for your queries: every schema decision should answer “How will I read this data?”
How it works step by step
Designing a schema for a blog platform in MongoDB can be broken into a repeatable process:
- Identify your entities — Typically
User,Post,Comment,Tag. Don’t forgetCategoryif needed. - Define your access patterns — List the top queries: fetch a post with its author, list comments for a post, display tags for a post, show all posts by a user, show recent posts by tag. Also list your write patterns: update post content, add a comment, add a tag.
- Decide embedding vs. referencing for each relationship —
- Post to Author: embed the author’s
usernameand_idinto the post? Or reference? Since users can change their name, you might embed a snapshot for denormalized reads, but keep the_idfor fetching details. - Post to Comments: comments can be unbounded — reference them in a separatecommentscollection. - Post to Tags: tags are a finite set, and you often query by tag — embed an array of strings and create an index. - Create your collections — typically
users,posts,comments,tags(if you want metadata). - Define your indexes — For example, index
posts.author_idandposts.created_atfor recent posts, andtagsarray for tag queries. - Iterate — Test with realistic data sizes. Adjust if a major query becomes slow or a write becomes awkward.
Cause-and-effect: if you embed comments, every new comment rewrites the entire post document, which is inefficient and can cause contention. If you reference tags, you end up with extra joins (or $lookup) on every post view.
Hands-on walkthrough
Let’s design a simple blog schema. We’ll use the mongosh shell and then show a Node.js example.
Step 1: Create collections
Start your MongoDB instance and switch to a blog database:
use blog
Step 2: Insert sample users
db.users.insertOne({
_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e2"),
username: "jane_doe",
email: "jane@example.com",
displayName: "Jane Doe"
})
Step 3: Insert a post that references the user and embeds tags
db.posts.insertOne({
title: "Hello MongoDB",
slug: "hello-mongodb",
content: "This is my first post...",
author_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e2"),
author_name: "Jane Doe", // denormalized for quick display
tags: ["mongodb", "tutorial"],
created_at: new Date(),
updated_at: new Date(),
status: "published"
})
Step 4: Insert comments as separate documents that reference the post
db.comments.insertMany([
{
post_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e2"),
user_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e3"),
content: "Great post!",
created_at: new Date()
},
{
post_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e2"),
user_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e4"),
content: "Thanks for sharing.",
created_at: new Date()
}
])
Step 5: Create indexes for common queries
db.posts.createIndex({ author_id: 1, created_at: -1 })
db.posts.createIndex({ tags: 1 })
db.comments.createIndex({ post_id: 1, created_at: 1 })
Expected Output
When you query for all comments for a post, you get a clean array of comment documents. When you query for recent posts by a tag, the index speeds up the result.
db.comments.find({ post_id: ObjectId("64a1b0c2e5f6f7a8b9c0d1e2") })
// Output: two comment documents
Pro tip: Use
$lookupin Aggregation only when you need full author data; otherwise, the denormalizedauthor_namesaves a query.
Node.js example
const { MongoClient } = require('mongodb');
async function run() {
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('blog');
const posts = db.collection('posts');
const post = {
title: 'MongoDB schema design',
slug: 'mongodb-schema-design',
content: '...',
author_id: new ObjectId(),
author_name: 'Jane Doe',
tags: ['mongodb', 'schema'],
created_at: new Date(),
updated_at: new Date(),
status: 'published'
};
const result = await posts.insertOne(post);
console.log(`Post inserted with _id: ${result.insertedId}`);
await client.close();
}
run().catch(console.error);
Compare options / when to choose what
The table below summarizes embedding vs. referencing for key blog relationships.
| Relationship | Embed? | Reference? | Recommendation |
|---|---|---|---|
| Post → Author | Embed username, _id |
Reference author_id for full data |
Embed display snippet, keep reference for details |
| Post → Comments | Embed array of comments | Separate comments collection |
Reference (comments are unbounded) |
| Post → Tags | Embed array of strings | Separate tags collection |
Embed tags as strings (simple, indexed) |
| Post → Category | Embed category name | Separate categories collection |
Embed for now; reference if you need category metadata |
| Comment → User | Embed username | Reference user_id |
Embed username for display; reference for profile |
When to choose what:
- Choose embedded tags when you want to query posts by tag without a join.
- Choose referenced comments when you anticipate high comment volume or want to add comments without rewriting the post.
- If you need atomic updates on a comment (e.g., edit), referencing is crucial — you can update a single comment document.
- If blog posts have a fixed number of fields and are read in full, embedding is fine.
Troubleshooting & edge cases
- Issue: Document size reaches 16 MB limit — If you embed comments, a post with many comments can exceed 16 MB. Fix: switch to referencing.
- Issue: N+1 query problem — When you reference author data, fetching 20 posts requires 20 extra user queries. Fix: denormalize
author_nameinto posts, or use$lookupin aggregation. - Issue: Stale data — If you embed
author_nameand the user changes their display name, old posts show outdated names. Fix: either live with it, or update posts on user name change, or always reference and incur an extra query. - Issue: Comment ordering — Without an index, comments come back in natural order (by
_id), not chronological. Fix: create an index onpost_id, created_atand sort explicitly. - Issue: Tag queries slow — Without an index on the
tagsarray, queries scan all documents. Fix: create a multikey index ontags. - Issue: Duplicate post slugs — Ensure uniqueness by creating a unique index on
slug, otherwise you might get duplicate URLs.
What you learned & what's next
You learned how to design a schema for a blog platform in MongoDB: you can now identify entities, map out access patterns, and decide between embedding and referencing. You applied the process to create users, posts, and comments collections, with denormalized author names and indexed tags. You also saw how to troubleshoot common pitfalls like document size limits and N+1 queries.
Key takeaways:
- Embed bounded, read-together data; reference unbounded or independently-updated data.
- Always design around your application queries.
- Use indexes to keep read performance high.
- Denormalize selectively to reduce joins but beware of consistency.
- Reference comments to avoid 16 MB limits.
What’s next: In the next lesson, we’ll dive into Advanced Querying — learning aggregation pipelines and more complex filtering, which builds directly on the schema you designed here. You'll use your blog schema to run $lookup, $unwind, and group operations to produce analytics.
Practice recap: Create a new collection
categoriesand reference it in your posts, then write an aggregation pipeline to list all posts with their category names. This will set you up for the next lesson.
Practice recap
Now it's your turn: extend the blog schema by adding a categories collection and reference it from your posts. Then write an aggregation pipeline that lists every post with its category name. If you’re feeling bold, add a view_counts array to each post and update it incrementally. This exercise will reinforce embedding vs. referencing and prepare you for advanced aggregation queries in the next lesson.
Common mistakes
- Embedding all comments inside the post document — you'll hit the 16 MB limit and suffer from slow writes on every new comment.
- Forgetting to add indexes on foreign keys like
author_idandpost_id— leads to full collection scans on common queries. - Denormalizing author data but never updating it when the user changes their name, causing stale display names across posts.
- Creating a unique index on
slugafter data is already inserted with duplicates — the index creation will fail; plan ahead.
Variations
- Use the Capped Collection pattern for recent comments if you only need the latest N comments per post, keeping storage bounded.
- Adopt a Time-Series collection pattern for view counts per post if you need analytics, storing daily counts as documents.
- Add a Materialized View (outlook) or aggregation pipeline to precompute author post counts when you have many posts per author.
Real-world use cases
- A news website storing articles with categories and tags, using embedded tags for fast filtering and referenced authors in a separate users collection.
- A developer blog platform where comments are a separate collection to support high volume and pagination, with posts referencing authors.
- A multi-author content management system that uses denormalized author names in posts to avoid frequent joins on listing pages.
Key takeaways
- Design your MongoDB schema around your application's read and write patterns, not around normalizing away duplication.
- Embed related data when it's always read together and bounded in size; reference when it's unbounded or independently updated.
- Create indexes on all foreign keys and frequently filtered fields to keep queries fast.
- Denormalization can eliminate joins but introduces consistency risks — use it judiciously.
- Comments should be stored in a separate collection to avoid 16 MB document limits and to allow atomic updates.
- Validate your schema against real data volumes before production to catch performance issues early.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.