Mongoose and query
1. BASIC CRUD (Create, Read, Update, Delete)
| Category | Command | Description |
|---|---|---|
| Create | Model.create(data) | Insert a new document |
new Model(data).save() | Create and save manually | |
| Read | Model.find() | Get all documents |
Model.findOne(filter) | Get one document that matches filter | |
Model.findById(id) | Get document by its _id | |
Model.find(filter, projection) | Filter and select specific fields | |
| Update | Model.updateOne(filter, update) | Update the first matching document |
Model.updateMany(filter, update) | Update multiple documents | |
Model.findByIdAndUpdate(id, update, options) | Update by ID and optionally return updated doc | |
| Delete | Model.deleteOne(filter) | Delete first matching document |
Model.deleteMany(filter) | Delete multiple documents | |
Model.findByIdAndDelete(id) | Delete by ID |
Examples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// User
[
{
"_id": "652a12c4d12e1a2b5f8f3d9a",
"name": "Alice",
"email": "alice@example.com",
"age": 25,
"status": "active",
"role": "user",
"createdAt": "2025-09-15T10:30:00Z"
},
{
"_id": "652a12c4d12e1a2b5f8f3d9b",
"name": "Bob",
"email": "bob@example.com",
"age": 30,
"status": "inactive",
"role": "admin",
"createdAt": "2025-08-12T09:45:00Z"
},
{
"_id": "652a12c4d12e1a2b5f8f3d9c",
"name": "Charlie",
"email": "charlie@example.com",
"age": 35,
"status": "active",
"role": "manager",
"createdAt": "2025-07-22T14:15:00Z"
},
{
"_id": "652a12c4d12e1a2b5f8f3d9d",
"name": "Diana",
"email": "diana@example.com",
"age": 28,
"status": "inactive",
"role": "user",
"createdAt": "2025-06-01T12:00:00Z"
},
{
"_id": "652a12c4d12e1a2b5f8f3d9e",
"name": "Ethan",
"email": "ethan@example.com",
"age": 40,
"status": "active",
"role": "superadmin",
"createdAt": "2025-05-18T16:45:00Z"
}
]
- CREATE
1
2
3
4
5
6
// Example 1: Simple create
await User.create({ name: "Alice", age: 25 });
// Example 2: Using constructor + save
const user = new User({ name: "Bob", age: 30 });
await user.save();
The first method inserts directly; the second creates an instance, allowing you to run pre-save hooks or validations before saving.
- READ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Example 1: Get all users
const users = await User.find();
// Example 2: Find by filter
const activeUsers = await User.find({ status: "active" });
// Example 3: Find one specific user
const user = await User.findOne({ email: "test@gmail.com" });
// Example 4: Find by ID
const user = await User.findById("652a12c4d12e1a2b5f8f3d9c");
// Example 5: Filter and select fields
const names = await User.find({}, "name email");
find()returns an array;findOne()andfindById()return a single document.- You can use projections (e.g., “name email”) to limit returned fields.
- UPDATE
1
2
3
4
5
6
7
8
9
10
11
12
// Example 1: Update one
await User.updateOne({ name: "Alice" }, { $set: { age: 26 } });
// Example 2: Update many
await User.updateMany({ status: "inactive" }, { $set: { status: "active" } });
// Example 3: Update by ID and return the new version
const updatedUser = await User.findByIdAndUpdate(
"652a12c4d12e1a2b5f8f3d9c",
{ $set: { age: 35 } },
{ new: true }
);
updateOneandupdateManymodify existing documents.findByIdAndUpdatelets you specify { new: true } to return the updated document instead of the old one.
- DELETE
1
2
3
4
5
6
7
8
// Example 1: Delete one
await User.deleteOne({ name: "Alice" });
// Example 2: Delete many
await User.deleteMany({ status: "inactive" });
// Example 3: Delete by ID
await User.findByIdAndDelete("652a12c4d12e1a2b5f8f3d9c");
deleteOneanddeleteManyremove data based on filters.findByIdAndDeleteremoves a document directly by _id.
2. COMMON QUERY PATTERNS
| Purpose | Example | Description |
|---|---|---|
| Get all | Model.find({}) | Fetch everything |
| Filter | Model.find({ age: 20 }) | Match certain fields |
| Comparison | { age: { $gt: 18 } }, { age: { $lte: 30 } } | $gt, $lt, $gte, $lte |
| Multiple conditions | { $and: [{ age: 20 }, { active: true }] } | Combine conditions |
| Value in list | { age: { $in: [18, 20, 25] } } | $in, $nin |
| Fuzzy search | { name: { $regex: 'john', $options: 'i' } } | Case-insensitive search |
| Check existence | { email: { $exists: true } } | Field existence |
| Null check | { field: null } | Match null fields |
Examples:
- Get all
Fetch every document in the users collection.
1
const users = await User.find({});
Returns all documents in the collection, equivalent to SELECT * FROM users in SQL.
- Filter
Find users who are exactly 20 years old.
1
const youngUsers = await User.find({ age: 20 });
Matches documents with age = 20.
- Comparison Operators
Find users older than 18 and younger or equal to 30.
1
const result = await User.find({ age: { $gt: 18, $lte: 30 } });
$gt = greater than, $lte = less than or equal.
- Multiple Conditions
Find users who are 20 years old and active.
1
const users = await User.find({ $and: [{ age: 20 }, { status: 'active' }] });
$and combines multiple filters; $or works similarly for alternatives.
- Value in List
Find users whose age is either 18, 20, or 25.
1
const users = await User.find({ age: { $in: [18, 20, 25] } });
$in checks for membership; $nin is the opposite (not in list).
- Regex (Regular Expression)
Find users whose name contains “john”, case-insensitive.
1
const users = await User.find({ name: { $regex: 'john', $options: 'i' } });
$regex supports pattern matching like SQL LIKE ‘%john%’.
- Check Existence
Find users who have an email field defined.
1
const users = await User.find({ email: { $exists: true } });
$exists returns documents whether a field is present or not.
- Null Check
Find users where the field nickname is null.
1
const users = await User.find({ nickname: null });
Matches documents where nickname is null or not defined.
3. MONGODB OPERATORS
| Type | Operators | Meaning |
|---|---|---|
| Comparison | $eq, $ne, $gt, $gte, $lt, $lte | Equals, not equals, greater/less than |
| Logical | $and, $or, $nor, $not | Combine logic conditions |
| Array | $in, $nin, $all, $size | Match array values |
| Update | $set, $inc, $push, $pull, $addToSet, $unset | Modify data |
| Check | $exists, $type | Check if field exists or its type |
- Comparison
Used in queries to filter by specific values or ranges.
1
2
3
4
5
6
7
8
9
10
11
// Equal to 25
await User.find({ age: { $eq: 25 } });
// Not equal to 'inactive'
await User.find({ status: { $ne: 'inactive' } });
// Greater than or equal to 30
await User.find({ age: { $gte: 30 } });
// Less than 40
await User.find({ age: { $lt: 40 } });
These operators are used for direct value comparisons — equivalent to SQL’s =, !=, >, <, >=, <=.
- Logical
Used to combine or negate multiple filter conditions.
1
2
3
4
5
6
7
8
// Match users who are active AND older than 25
await User.find({ $and: [{ status: 'active' }, { age: { $gt: 25 } }] });
// Match users who are either admin OR manager
await User.find({ $or: [{ role: 'admin' }, { role: 'manager' }] });
// Match users who are NOT active
await User.find({ status: { $not: { $eq: 'active' } } });
$and, $or, $not, $nor help you build complex query logic.
- Array Operators
Used when working with arrays inside documents.
1
2
3
4
5
6
7
8
// Match if 'skills' contains at least one of these values
await User.find({ skills: { $in: ['NodeJS', 'React'] } });
// Match if 'skills' includes ALL listed values
await User.find({ skills: { $all: ['MongoDB', 'Express'] } });
// Match arrays with exactly 3 items
await User.find({ skills: { $size: 3 } });
$in and $nin check membership; $all checks that all specified values are present; $size checks array length.
- Update Operators
Used in updateOne(), updateMany(), or findByIdAndUpdate() to modify data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Set a new field value
await User.updateOne({ name: 'Alice' }, { $set: { status: 'active' } });
// Increment a number field
await User.updateOne({ name: 'Bob' }, { $inc: { age: 1 } });
// Add an item to an array
await User.updateOne({ name: 'Charlie' }, { $push: { skills: 'Docker' } });
// Remove an item from an array
await User.updateOne({ name: 'Charlie' }, { $pull: { skills: 'React' } });
// Add only if not already in array
await User.updateOne({ name: 'Ethan' }, { $addToSet: { skills: 'AI' } });
// Remove a field completely
await User.updateOne({ name: 'Diana' }, { $unset: { nickname: "" } });
hese are essential for updating documents without overwriting the entire object.
- Check Operators
Used to inspect whether a field exists or to verify its type.
1
2
3
4
5
// Find documents that have an 'email' field
await User.find({ email: { $exists: true } });
// Find where 'age' is stored as a Number
await User.find({ age: { $type: 'number' } });
- $exists returns documents with or without a given field.
- $type checks BSON type (e.g., number, string, date, objectId).
4. LIMIT, SORT, PAGINATION
| Feature | Example | Description |
|---|---|---|
| Limit | .limit(10) | Limit number of results |
| Skip | .skip(20) | Skip first N results |
| Sort | .sort({ age: 1 }) | 1 = ascending, -1 = descending |
| Select fields | .select('name age') | Include only certain fields |
| Exclude fields | .select('-password -email') | Exclude specific fields |
Examples
- Limit results
Retrieve only the first 10 users from the database.
1
const users = await User.find().limit(10);
Used for efficiency — prevents returning too many records at once.
- Skip results
Skip the first 20 users and return the rest.
1
const users = await User.find().skip(20);
Commonly used in pagination systems to “jump” to a later set of results.
- Sort results
Sort users by age in ascending or descending order.
1
2
3
4
5
// Ascending (youngest first)
const ascUsers = await User.find().sort({ age: 1 });
// Descending (oldest first)
const descUsers = await User.find().sort({ age: -1 });
You can also sort by multiple fields:
1
await User.find().sort({ status: 1, age: -1 });
- Skip results
Retrieve only specific fields like name and age.
1
const users = await User.find().select('name age');
- Equivalent to SQL SELECT name, age FROM users;
- Reduces data load by excluding unnecessary fields.
- Exclude fields
Hide sensitive or unnecessary data such as passwords or emails.
1
const users = await User.find().select('-password -email');
The minus sign (-) means “exclude this field” — useful for security.
- Combine for pagination
Typical pagination query example:
1
2
3
4
5
6
7
8
const page = 2;
const limit = 5;
const skip = (page - 1) * limit;
const users = await User.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit);
This retrieves page 2, with 5 users per page, sorted by newest creation date.
5. POPULATE (JOIN BETWEEN COLLECTIONS)
1
const posts = await Post.find().populate('author', 'name email');
| Command | Description |
|---|---|
.populate('refField') | Fetch referenced document |
.populate('refField', 'name age') | Fetch only selected fields |
.populate({ path: 'author', match: { active: true } }) | Apply filter inside populate |
Examples:
JSON Plus for Posts collections:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
{
"_id": "700a12c4d12e1a2b5f8f3aaa",
"title": "AI in Business",
"content": "Using AI to optimize workflows.",
"author": "652a12c4d12e1a2b5f8f3d9a",
"likes": 45,
"tags": ["AI", "Business"],
"createdAt": "2025-09-16T09:00:00Z"
},
{
"_id": "700a12c4d12e1a2b5f8f3aab",
"title": "Managing Teams Effectively",
"content": "Leadership strategies for modern managers.",
"author": "652a12c4d12e1a2b5f8f3d9c",
"likes": 23,
"tags": ["Leadership", "HR"],
"createdAt": "2025-09-20T14:30:00Z"
},
{
"_id": "700a12c4d12e1a2b5f8f3aac",
"title": "System Architecture",
"content": "Designing scalable NodeJS backends.",
"author": "652a12c4d12e1a2b5f8f3d9b",
"likes": 58,
"tags": ["Tech", "Backend"],
"createdAt": "2025-09-25T10:00:00Z"
}
]
- Basic populate (simple JOIN)
Fetch all posts and include full author details.
1
const posts = await Post.find().populate('author');
Output:
1
2
3
4
5
6
7
8
9
10
[
{
"title": "AI in Business",
"author": { "name": "Alice", "email": "alice@example.com", "role": "user" }
},
{
"title": "System Architecture",
"author": { "name": "Bob", "email": "bob@example.com", "role": "admin" }
}
]
- Populate only specific fields
Fetch only the author’s name and email.
1
const posts = await Post.find().populate('author', 'name email');
Output:
1
2
3
4
5
6
[
{
"title": "AI in Business",
"author": { "name": "Alice", "email": "alice@example.com" }
}
]
- Populate with filter (match condition)
Only populate authors whose status is “active”.
1
2
3
4
5
const posts = await Post.find().populate({
path: 'author',
match: { status: 'active' },
select: 'name email status'
});
Output:
1
2
3
4
5
6
7
8
9
10
[
{
"title": "AI in Business",
"author": { "name": "Alice", "email": "alice@example.com", "status": "active" }
},
{
"title": "System Architecture",
"author": null // Bob was filtered out (inactive)
}
]
- Nested populate (populate inside populate)
Assume there’s another collection Comment that references both Post and User:
1
2
3
4
5
6
const Comment = mongoose.model('Comment', new mongoose.Schema({
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
content: String,
createdAt: Date
}));
Now fetch all comments, including both:
- The user who wrote the comment, and
- The post details, including its author.
1
2
3
4
5
6
const comments = await Comment.find()
.populate({
path: 'post',
populate: { path: 'author', select: 'name email' }
})
.populate('user', 'name');
Output:
1
2
3
4
5
6
7
8
9
10
[
{
"content": "Great article!",
"user": { "name": "Diana" },
"post": {
"title": "AI in Business",
"author": { "name": "Alice", "email": "alice@example.com" }
}
}
]
- Populate multiple references at once
If your Post schema had both author and reviewer, you can populate both in one query:
1
2
3
const posts = await Post.find()
.populate('author', 'name')
.populate('reviewer', 'name');
6. AGGREGATION PIPELINE
| Stage | Example | Description |
|---|---|---|
$match | { $match: { active: true } } | Filter documents |
$group | { $group: { _id: '$status', count: { $sum: 1 } } } | Group and count |
$sort | { $sort: { age: -1 } } | Sort results |
$project | { $project: { name: 1, age: 1 } } | Select fields |
$lookup | Lookup another collection (JOIN) | |
$unwind | Deconstruct an array field |
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const result = await User.aggregate([
{ $match: { status: 'active' } },
{ $lookup: {
from: 'posts',
localField: '_id',
foreignField: 'author',
as: 'posts'
}},
{ $unwind: '$posts' },
{ $group: {
_id: '$name',
totalPosts: { $sum: 1 },
avgLikes: { $avg: '$posts.likes' }
}},
{ $sort: { totalPosts: -1 } }
]);
- $match: only active users
- $lookup: join user with their posts
- $unwind: flatten the array of posts
- $group: group by user and count/average
- $sort: show users with most posts first
7. COUNT & EXISTENCE CHECKS
| Command | Description |
|---|---|
countDocuments(filter) | Count documents that match filter |
estimatedDocumentCount() | Fast count of all documents |
exists(filter) | Returns true/false if document exists |
Examples:
1
2
3
4
5
6
7
8
9
10
// Count all active users
const activeCount = await User.countDocuments({ status: 'active' });
// Get total number of users quickly
const totalCount = await User.estimatedDocumentCount();
// Check if a user with a given email exists
const isExisting = await User.exists({ email: 'alice@example.com' });
console.log({ activeCount, totalCount, isExisting });
8. COMBINED ADVANCED QUERY EXAMPLE
Get a list of active users along with their posts, show only selected fields, compute total posts and average likes per user, then sort by total posts (descending).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const result = await User.aggregate([
// 1. Filter only active users
{
$match: { status: 'active' }
},
// 2. Join with Post collection (JOIN equivalent)
{
$lookup: {
from: 'posts',
localField: '_id',
foreignField: 'author',
as: 'posts'
}
},
// 3. Add computed fields (totalPosts, avgLikes)
{
$addFields: {
totalPosts: { $size: '$posts' },
avgLikes: { $avg: '$posts.likes' }
}
},
// 4. Project only desired fields
{
$project: {
name: 1,
email: 1,
totalPosts: 1,
avgLikes: { $round: ['$avgLikes', 1] }
}
},
// 5. Sort descending
{
$sort: { totalPosts: -1 }
}
]);