MongoDB
MongoDB queries, aggregation, indexing, schema design, and Mongoose ODM
Questions6 shown
MongoDB is a NoSQL document database that stores data in flexible, JSON-like documents (called BSON) instead of rows and columns.
MongoDB vs SQL — A simple comparison:
| SQL (MySQL, PostgreSQL) | MongoDB |
|------------------------|---------|
| Tables | Collections |
| Rows | Documents |
| Columns | Fields |
| Fixed schema | Flexible schema |
| JOIN to combine tables | Embed or Reference |
| SQL query language | MQL (MongoDB Query Language) |
A MongoDB document looks like this:
{
"_id": "ObjectId('...')",
"name": "Ravish",
"email": "ravish@example.com",
"skills": ["JavaScript", "React", "Node.js"],
"address": {
"city": "Delhi",
"country": "India"
}
}Notice how you can have arrays and nested objects right inside the document — no need for separate tables and JOINs.
When to choose MongoDB:
Your data structure changes frequently
You need to store varied, complex data
You want fast development iterations
Horizontal scaling is important
When SQL might be better:
Your data has strict relationships (banking, accounting)
You need complex transactions across multiple tables
Data integrity is the top priority
Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It adds structure and convenience on top of the raw MongoDB driver.
Think of it as a helpful layer between your code and MongoDB:
Without Mongoose (raw driver):
await db.collection('users').insertOne({
name: 123, // Oops, name should be a string!
email: 'not-an-email' // No validation
});With Mongoose:
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, match: /.+@.+/ },
age: { type: Number, min: 13 }
});
const User = mongoose.model('User', userSchema);
await User.create({ name: 123 }); // Error! Validation failsWhat Mongoose gives you:
| Feature | Benefit |
|---------|--------|
| Schemas | Define the shape of your data |
| Validation | Auto-validate before saving |
| Type casting | Converts strings to numbers, etc. |
| Middleware/Hooks | Run code before/after save, update, delete |
| Population | Easily load related documents (like SQL JOINs) |
| Query helpers | Chainable, readable queries |
| Virtuals | Computed properties that don't get saved |
Mongoose makes working with MongoDB feel structured and safe while keeping all the flexibility of NoSQL.
Here's a complete CRUD reference using Mongoose:
CREATE:
// Method 1: Create and save
const user = new User({ name: 'Ravish', email: 'r@test.com' });
await user.save();
// Method 2: Direct create
const user = await User.create({ name: 'Ravish', email: 'r@test.com' });
// Create many
await User.insertMany([{ name: 'A' }, { name: 'B' }]);READ:
// Find all
const users = await User.find();
// Find with filter
const devs = await User.find({ role: 'developer' });
// Find one
const user = await User.findOne({ email: 'r@test.com' });
// Find by ID
const user = await User.findById('64a...');
// Select specific fields
const names = await User.find().select('name email');
// Sort, limit, skip (pagination)
const page = await User.find().sort({ createdAt: -1 }).skip(20).limit(10);UPDATE:
// Update one
await User.updateOne({ _id: id }, { $set: { name: 'New Name' } });
// Find, update, return new doc
const updated = await User.findByIdAndUpdate(
id,
{ name: 'New Name' },
{ new: true, runValidators: true }
);DELETE:
await User.deleteOne({ _id: id });
const deleted = await User.findByIdAndDelete(id);
await User.deleteMany({ status: 'inactive' });Pro tip: Always use { new: true } with findByIdAndUpdate to get the updated document back.
Indexes are like the index at the back of a book — instead of reading every page to find a topic, you look up the index and jump directly to the right page.
Without indexes, MongoDB does a collection scan — checking every single document. With indexes, it jumps straight to the matches.
Creating indexes with Mongoose:
const userSchema = new Schema({
email: { type: String, unique: true, index: true },
username: { type: String, index: true },
age: Number,
city: String
});
// Compound index (for queries that filter by both)
userSchema.index({ city: 1, age: -1 });
// Text index (for search)
userSchema.index({ name: 'text', bio: 'text' });Performance difference:
Without index: Scan 1,000,000 documents → 500ms
With index: Lookup index → 2msTypes of indexes:
| Type | Use case |
|------|----------|
| Single field | { email: 1 } — Most common |
| Compound | { city: 1, age: -1 } — Multi-field queries |
| Unique | Prevents duplicate values |
| Text | Full-text search |
| TTL | Auto-delete after time (sessions, logs) |
When to index:
Fields you frequently search/filter by
Fields used in .sort()
Fields used in .populate() references
When NOT to index:
Fields rarely queried
Collections with very few documents
Too many indexes slow down writes (each insert must update all indexes)
This is one of the most important decisions in MongoDB schema design.
Embedding — Put related data inside the same document:
const orderSchema = new Schema({
customer: String,
items: [
{
name: String,
price: Number,
quantity: Number
}
]
});Referencing — Store a reference (ID) and look it up later:
const orderSchema = new Schema({
customer: { type: Schema.Types.ObjectId, ref: 'User' },
products: [{ type: Schema.Types.ObjectId, ref: 'Product' }]
});
// To get the full data:
const order = await Order.findById(id).populate('customer products');When to EMBED:
Data is always accessed together (user + address)
One-to-few relationships (a blog post + its comments)
Data doesn't change independently
You want fast reads (single query)
When to REFERENCE:
Data is accessed independently
One-to-many or many-to-many (users + orders)
Data is large or grows unboundedly
Data changes frequently and independently
Quick decision guide:
Accessed together? → Embed
Grows without limit? → Reference
Needs to be updated independently? → Reference
Small and rarely changes? → EmbedThe 16MB document limit in MongoDB also matters — if embedded data could grow very large, reference it instead.
MongoDB has powerful query operators that start with `$`. Here are the ones you'll use most: **Comparison:** [code block] **Logical:** [code block] **Array:** [code block] **Update operators:** [c...
Create a free account to unlock login-level content, or go premium for everything.
Interview Preparation
PremiumHigh-signal questions that come up most in real interviews. These are the ones worth spending extra time on.
The **Aggregation Pipeline** is MongoDB's way of processing and transforming data — like a conveyor belt where each stage transforms the documents. [code block] **Common stages:** | Stage | What it...
Create a free account to unlock login-level content, or go premium for everything.
Mongoose **middleware** (also called hooks) lets you run code **before or after** certain operations. They're like interceptors for your database operations. **Pre hooks** — Run BEFORE the operation:...
Create a free account to unlock login-level content, or go premium for everything.
**Population** is Mongoose's way of replacing an ObjectId reference with the actual document from another collection. It's the MongoDB equivalent of a SQL JOIN — but it happens at the application leve...
Create a free account to unlock login-level content, or go premium for everything.
Mongoose has a powerful built-in validation system that runs automatically before saving to the database. **Built-in validators:** [code block] **Custom validators:** [code block] **Handling valida...
Create a free account to unlock login-level content, or go premium for everything.
**Transactions** let you group multiple operations together — either they ALL succeed, or NONE of them do. This is essential for operations that must be atomic. **Example: Transferring money between ...
Create a free account to unlock login-level content, or go premium for everything.
**Virtuals** are properties that are computed on the fly — they don't get stored in the database. [code block] **Instance methods** — Add custom functions to documents: [code block] **Static method...
Create a free account to unlock login-level content, or go premium for everything.
This is one of the most practical questions you'll face in interviews and real projects. **Choose MongoDB when:** - Your data structure is **flexible or evolves** frequently - You're building a **pro...
Create a free account to unlock login-level content, or go premium for everything.
There are two main approaches to pagination: **1. Skip-Limit (Offset-based) — Simple but slower for large datasets:** [code block] **2. Cursor-based — Better for large datasets and infinite scroll:*...
Create a free account to unlock login-level content, or go premium for everything.
Good schema design is the foundation of a performant MongoDB application. **1. Design based on access patterns, not relationships:** [code block] **2. Embed data you read together:** [code block] *...
Create a free account to unlock login-level content, or go premium for everything.