π― CRUD Operations: Create, Read, Update, Delete in MongoDB
Understanding MongoDB documents and BSON - the secret sauce behind MongoDB's flexibility and power
π The Tale of Two Developers: SQL Tables vs MongoDB Documents
Meet Sarah (using MySQL) and Alex (using MongoDB). Both are building a social media app. Let's watch their struggle with storing user profiles...
Sarah's SQL Struggle
The Rigid Structure Problem
Day 1: Creating User Table
"Perfect! Simple and clean!" π
Day 5: Boss wants to add phone numbers!
"Okay... added a new table." π
Day 10: Now add addresses, hobbies, work history...
"This is a NIGHTMARE! 7 tables just for one user profile!" π
Alex's MongoDB Magic
The Flexible Document Way
Day 1 to Day 10: ONE Document for Everything!
"One query, one document, all the data! Life is good!" π―β¨
π‘ The MongoDB Document Philosophy
"Store data the way you USE it, not the way databases FORCE you to!"
MongoDB documents mirror your application's objects - natural, flexible, and powerful! π
π CRUD Workflow
π CREATE Operations
Insert new documents into collections.
insertOne() - Add Single Document
db.users.insertOne({
name: "Alice Johnson",
email: "alice@example.com",
age: 25,
city: "New York"
})
Returns: { acknowledged: true, insertedId: ObjectId("...") }
insertMany() - Add Multiple Documents
db.users.insertMany([
{ name: "Bob", email: "bob@ex.com", age: 30 },
{ name: "Carol", email: "carol@ex.com", age: 28 },
{ name: "Dave", email: "dave@ex.com", age: 35 }
])
π» Try CREATE Operations Live
// Click "Run" to see the result...
π READ Operations
Query and retrieve documents from collections.
find() - Get Multiple Documents
// Find all users
db.users.find()
// Find users in New York
db.users.find({ city: "New York" })
// Find users aged 25-35
db.users.find({ age: { $gte: 25, $lte: 35 } })
Query Operators
- $eq - Equal
- $ne - Not equal
- $gt - Greater than
- $gte - Greater or equal
- $lt - Less than
- $lte - Less or equal
- $and - All conditions
- $or - Any condition
- $not - Negate
- $in - Match array
- $nin - Not in array
π» Try READ Operations Live
// Click "Run" to see the result...
βοΈ UPDATE Operations
Modify existing documents.
updateOne() - Update Single Document
db.users.updateOne(
{ email: "alice@example.com" },
{ $set: { age: 26, city: "Boston" } }
)
Update Operators
- $set - Set field value
- $inc - Increment number
- $push - Add to array
- $pull - Remove from array
- $unset - Delete field
// Increment age by 1
db.users.updateOne(
{ name: "Alice" },
{ $inc: { age: 1 } }
)
// Add hobby to array
db.users.updateOne(
{ name: "Alice" },
{ $push: { hobbies: "reading" } }
)
π» Try UPDATE Operations Live
// Click "Run" to see the result...
ποΈ DELETE Operations
β οΈ Use carefully! Deletions are permanent.
deleteOne() - Delete Single Document
db.users.deleteOne({ email: "alice@example.com" })
deleteMany() - Delete Multiple Documents
// Delete all inactive users
db.users.deleteMany({ isActive: false })
// β οΈ NEVER do this - deletes EVERYTHING!
db.users.deleteMany({})
- Always test filter with find() first
- Consider soft deletes (mark as deleted)
- Take backups before bulk deletes
- Require confirmation in production
π» Try DELETE Operations Live β οΈ Simulated
// Click "Run" to see the result...
π Real-World Document Examples
Example 1: User Profile (Social Media)
{
_id: ObjectId("507f1f77bcf86cd799439011"),
username: "johndoe",
email: "[email protected]",
passwordHash: "$2b$10$...",
profile: {
fullName: "John Doe",
bio: "Software Developer | Coffee Lover",
avatar: "https://cdn.example.com/avatar.jpg",
dateOfBirth: ISODate("1995-06-15"),
location: {
city: "New York",
country: "USA"
}
},
followers: [ObjectId("..."), ObjectId("...")],
following: [ObjectId("..."), ObjectId("...")],
stats: {
postsCount: 245,
followersCount: 1250,
followingCount: 380
},
preferences: {
theme: "dark",
language: "en",
notifications: {
email: true,
push: true,
sms: false
}
},
createdAt: ISODate("2023-01-15T10:30:00Z"),
updatedAt: ISODate("2024-12-08T15:45:00Z"),
isVerified: true,
isActive: true
}
Example 2: E-commerce Product
{
_id: ObjectId("65a1b2c3d4e5f6789"),
sku: "LAPTOP-XPS13-2024",
name: "Dell XPS 13 Laptop",
description: "13.4-inch FHD+ display, Intel i7...",
category: "Electronics",
subcategory: "Laptops",
brand: "Dell",
price: {
amount: NumberDecimal("1299.99"),
currency: "USD",
discount: {
percentage: 15,
validUntil: ISODate("2024-12-31")
}
},
inventory: {
inStock: 25,
reserved: 3,
warehouse: "NYC-01"
},
images: [
{
url: "https://cdn.example.com/laptop1.jpg",
isPrimary: true
},
{
url: "https://cdn.example.com/laptop2.jpg",
isPrimary: false
}
],
specifications: {
processor: "Intel Core i7-1355U",
ram: "16GB DDR5",
storage: "512GB NVMe SSD",
display: "13.4\" FHD+ (1920x1200)",
weight: "2.7 lbs"
},
tags: ["laptop", "dell", "ultrabook", "portable"],
rating: {
average: 4.7,
count: 348
},
reviews: [ObjectId("..."), ObjectId("...")], // References
createdAt: ISODate("2024-01-10T08:00:00Z"),
updatedAt: ISODate("2024-12-08T14:30:00Z"),
isActive: true,
isFeatured: true
}
Example 3: Blog Post with Comments
{
_id: ObjectId("..."),
title: "Getting Started with MongoDB",
slug: "getting-started-with-mongodb",
content: "MongoDB is a NoSQL database that...",
excerpt: "Learn the basics of MongoDB...",
author: {
id: ObjectId("..."),
name: "Jane Smith",
email: "[email protected]"
},
tags: ["mongodb", "nosql", "database", "tutorial"],
categories: ["Databases", "Tutorials"],
featuredImage: "https://cdn.example.com/mongodb.jpg",
stats: {
views: 15420,
likes: 892,
shares: 234
},
comments: [
{
id: ObjectId("..."),
userId: ObjectId("..."),
userName: "Bob Johnson",
comment: "Great tutorial! Very helpful.",
createdAt: ISODate("2024-12-05T10:30:00Z"),
likes: 12
},
{
id: ObjectId("..."),
userId: ObjectId("..."),
userName: "Alice Cooper",
comment: "Thanks for sharing!",
createdAt: ISODate("2024-12-06T14:15:00Z"),
likes: 8
}
],
seo: {
metaTitle: "MongoDB Tutorial - Complete Guide",
metaDescription: "Learn MongoDB from scratch...",
keywords: ["mongodb", "tutorial", "nosql"]
},
publishedAt: ISODate("2024-12-01T09:00:00Z"),
updatedAt: ISODate("2024-12-08T11:20:00Z"),
status: "published",
isFeatured: true
}
β Best Practices for Document Design
Design for Your Queries
Structure documents based on how you'll query them. If you always need user + address together, embed address. If you query them separately, use references.
E-commerce: Embed user's current cart items (always queried together). Reference past orders (queried separately).
Avoid Unbounded Arrays
Arrays that grow without limit will hit the 16 MB document size limit. Use references for unlimited relationships.
user.orders: [...] // Unlimited
order.userId: ObjectId(...)
Use Appropriate Data Types
Choose the right BSON type. Use NumberDecimal for money, Date for timestamps, ObjectId for IDs.
β price: NumberDecimal("99.99") - Precise!β price: 99.99 - Float rounding errors!Keep Documents Under 16 MB
This is a hard limit. For large files (images, videos), use GridFS. For large arrays, use references.
- Don't embed large binary data
- Limit array size (< 1000 items ideal)
- Use GridFS for files > 16 MB
Plan for Schema Evolution
MongoDB is schema-less but plan for changes. Add new fields easily, but consider backward compatibility.
Use default values in code. Old documents without new fields? No problem! Just return defaults.
β Interview Questions & Answers
Answer:
A MongoDB document is a data structure composed of field-value pairs, similar to JSON objects. It's stored in BSON (Binary JSON) format internally.
Key Differences from SQL rows:
- Schema Flexibility: Documents in the same collection can have different fields. SQL rows must have same columns.
- Nested Data: Documents support embedded documents and arrays. SQL requires separate tables and JOINs.
- NULL handling: MongoDB: just omit field. SQL: must use NULL, wastes space.
- Data Types: BSON supports more types (Date, Binary, ObjectId) than SQL's basic types.
Example: In MongoDB, one user document can have "age" field while another doesn't. In SQL, all rows must have age column (even if NULL).
Answer:
BSON stands for Binary JSON. MongoDB stores documents in BSON format internally, though you work with JSON.
Why BSON instead of JSON?
- Speed: Binary format is faster to parse and traverse. No string parsing needed.
- Rich Data Types: BSON supports Date, Binary, ObjectId, NumberDecimal - JSON only has string, number, boolean, array, object, null.
- Efficient Storage: BSON includes document length at the beginning, making it easy to skip documents without parsing entire content.
- Indexing: Binary format makes indexing more efficient.
The Flow: You write JSON β MongoDB converts to BSON β Stores BSON β Returns JSON to you. Conversion is automatic!
Answer:
Use Embedding When:
- One-to-One or One-to-Few relationships: User β Address (one address)
- Data accessed together: Always show user with their address
- Data doesn't change independently: Address only changes when user updates it
- Bounded array: Blog post β 50 comments (limited, won't grow huge)
Use References When:
- One-to-Many or Many-to-Many: User β Unlimited Orders
- Data changes independently: Product price changes, but order history stays same
- Unbounded arrays: User β Followers (can be millions)
- Large documents: Embedding would exceed 16 MB limit
Example: Embed user's current shopping cart (small, accessed together). Reference user's past orders (unlimited, queried separately).
Answer:
Maximum document size is 16 MB.
Reasons for the limit:
- Memory Efficiency: Large documents consume more RAM during queries. 16 MB keeps memory usage reasonable.
- Network Performance: Huge documents slow down network transfer. 16 MB is good balance.
- Encourages Good Design: Forces you to use references for large/unbounded data instead of embedding everything.
What if you need more?
- Use GridFS for files > 16 MB (images, videos, large files)
- Split large arrays into separate collection with references
- Store large text in external storage (S3) and keep URL in document
Note: 16 MB is huge! That's ~16,000 pages of text. Most documents are < 1 KB.
Answer:
ObjectId is a 12-byte unique identifier automatically generated by MongoDB for each document's _id field.
Structure (12 bytes total):
- 4 bytes: Timestamp (seconds since Unix epoch) - Allows sorting by creation time!
- 5 bytes: Random value (unique per machine and process)
- 3 bytes: Incrementing counter
Example: ObjectId("507f1f77bcf86cd799439011")
Benefits:
- Globally Unique: No collisions even across distributed systems
- No coordination needed: Generated locally, no central server
- Sortable by time: First 4 bytes = timestamp, so sorting by _id = sorting by creation time
- Lightweight: Only 12 bytes vs UUIDs which are 16 bytes
Extract timestamp: ObjectId("...").getTimestamp() gives you document creation time!
Answer:
MongoDB is schema-less (flexible schema), making schema evolution very easy.
How it works:
- No Migration Needed: Just start inserting documents with new fields. No ALTER TABLE!
- Backward Compatible: Old documents without new fields continue to work
- Forward Compatible: New code can handle old documents (use defaults for missing fields)
Example Scenario:
Old documents: { name: "John", email: "..." }
Add phone field: Just insert { name: "Alice", email: "...", phone: "..." }
Query old docs: Return phone = null or default value in code
Best Practice: Handle missing fields in application code with default values. No database migration needed!
Optional Validation: MongoDB 3.6+ supports schema validation if you want to enforce structure.