MongoDB is a document-oriented NoSQL database that stores data as flexible, JSON-like documents called BSON. At the heart of every MongoDB application lies CRUD â Create, Read, Update, and Delete â the four fundamental operations used to interact with data. This tutorial walks through each operation in detail, from beginner basics to advanced patterns, using the official MongoDB Node.js Driver.
Information
đ 1. Introduction
Every database interaction ultimately boils down to one of four operations: creating new data, reading existing data, updating data, or deleting data. MongoDB provides a rich, expressive API for each of these operations, allowing developers to work with data in a natural, JavaScript-friendly way.
đ§Š 2. CRUD Overview
Each CRUD operation in MongoDB is exposed through methods on a Collection object. Before performing any operation, you must first connect to a MongoDB instance and select a database and collection.
Connecting to MongoDB
const { MongoClient } = require("mongodb");
const client = new MongoClient("mongodb://localhost:27017");
async function main() {
await client.connect();
const db = client.db("shop");
const collection = db.collection("products");
// CRUD operations go here
}
main().catch(console.error);| Operation | Purpose | Common Methods |
|---|---|---|
| Create | Insert new documents | insertOne(), insertMany() |
| Read | Query existing documents | find(), findOne() |
| Update | Modify existing documents | updateOne(), updateMany() |
| Delete | Remove documents | deleteOne(), deleteMany() |
â 3. Creating Documents
The Create operation adds new documents to a collection. MongoDB automatically generates a unique _id field for each document unless you explicitly provide one.
insertOne()
Use insertOne() to insert a single document into a collection.
Inserting a single document
const result = await collection.insertOne({
name: "Wireless Mouse",
price: 25.99,
inStock: true
});
console.log(result.insertedId);Tip
insertMany()
Use insertMany() to insert multiple documents in a single, efficient database call.
Inserting multiple documents
const result = await collection.insertMany([
{ name: "Keyboard", price: 49.99 },
{ name: "Monitor", price: 199.99 },
{ name: "Webcam", price: 39.99 }
]);
console.log(result.insertedCount);Warning
đ 6. Reading Documents
The Read operation retrieves documents from a collection based on specified criteria. MongoDB's query language is flexible, supporting exact matches, comparisons, and complex logical conditions.
find()
find() returns a cursor pointing to all documents matching a query. An empty filter {} returns every document in the collection.
Finding all matching documents
const cursor = collection.find({ inStock: true });
const products = await cursor.toArray();
console.log(products);findOne()
findOne() returns the first matching document only, or null if no document matches.
Finding a single document
const product = await collection.findOne({ name: "Keyboard" });
console.log(product);đ¯ 9. Query Filters
Query filters use comparison and logical operators to narrow down results. Filters are written as objects where keys map to fields and values describe the matching criteria.
| Operator | Meaning | Example |
|---|---|---|
| $eq | Equal to | { price: { $eq: 25 } } |
| $gt / $gte | Greater than / or equal | { price: { $gt: 50 } } |
| $lt / $lte | Less than / or equal | { price: { $lt: 100 } } |
| $in | Matches any value in an array | { name: { $in: ["Mouse","Keyboard"] } } |
| $and / $or | Logical combinations | { $or: [{ inStock: true }, { price: { $lt: 10 } }] } |
Combining multiple filter conditions
const results = await collection.find({
price: { $gte: 20, $lte: 100 },
inStock: true
}).toArray();đī¸ 10. Projection
Projection controls which fields are returned in query results, reducing network overhead by excluding unnecessary data.
Applying a projection
// Include only name and price, exclude _id
const results = await collection
.find({}, { projection: { name: 1, price: 1, _id: 0 } })
.toArray();Note
âī¸ 11. Sorting
The sort() method orders results by one or more fields. Use 1 for ascending order and -1 for descending order.
Sorting by price (descending)
const results = await collection
.find({})
.sort({ price: -1 })
.toArray();đĸ 12. Limiting Results
limit() restricts the number of documents returned, which is especially useful for pagination.
Limiting query results
const topThree = await collection
.find({})
.sort({ price: -1 })
.limit(3)
.toArray();âī¸ 13. Skipping Results
skip() bypasses a specified number of documents, commonly combined with limit() to implement paginated queries.
Paginating with skip() and limit()
const page2 = await collection
.find({})
.sort({ _id: 1 })
.skip(10)
.limit(10)
.toArray();Caution
đĸ 14. Counting Documents
Use countDocuments() to count how many documents match a given filter.
Counting matching documents
const count = await collection.countDocuments({ inStock: true });
console.log(`In-stock products: ${count}`);âī¸ 15. Updating Documents
The Update operation modifies existing documents. MongoDB uses update operators (prefixed with $) to describe precisely how a document should change.
updateOne()
Updates the first document that matches the filter.
Updating a single document
const result = await collection.updateOne(
{ name: "Mouse" },
{ $set: { price: 22.99 } }
);
console.log(result.modifiedCount);updateMany()
Updates all documents matching the filter.
Updating multiple documents
const result = await collection.updateMany(
{ inStock: false },
{ $set: { discontinued: true } }
);
console.log(result.modifiedCount);replaceOne()
Unlike updateOne(), which modifies specific fields, replaceOne() replaces the entire document (except _id) with a new one.
Replacing a document entirely
const result = await collection.replaceOne(
{ name: "Webcam" },
{ name: "Webcam Pro", price: 59.99, inStock: true }
);Important
đ ī¸ 19. Update Operators
| Operator | Description |
|---|---|
| $set | Sets the value of a field |
| $unset | Removes a field |
| $inc | Increments a numeric field |
| $push | Appends a value to an array |
| $pull | Removes a value from an array |
| $rename | Renames a field |
Using multiple update operators together
await collection.updateOne(
{ name: "Mouse" },
{
$inc: { stockCount: -1 },
$push: { tags: "electronics" }
}
);đī¸ 20. Deleting Documents
The Delete operation removes documents that match a given filter. Deleted documents cannot be recovered unless backups exist.
deleteOne()
Deleting a single document
const result = await collection.deleteOne({ name: "Webcam Pro" });
console.log(result.deletedCount);deleteMany()
Deleting multiple documents
const result = await collection.deleteMany({ discontinued: true });
console.log(result.deletedCount);Danger
âī¸ 23. Bulk Operations
bulkWrite() allows you to combine multiple insert, update, and delete operations into a single database round-trip, improving performance for batch workloads.
Combining operations with bulkWrite()
const result = await collection.bulkWrite([
{ insertOne: { document: { name: "USB Hub", price: 15.99 } } },
{ updateOne: {
filter: { name: "Mouse" },
update: { $set: { price: 19.99 } }
}
},
{ deleteOne: { filter: { name: "Old Item" } } }
]);
console.log(result);đ 24. Upserts
An upsert (update + insert) creates a new document if no document matches the filter, or updates the existing one if a match is found. Enable this behavior with the upsert option.
Using upsert to insert-or-update
const result = await collection.updateOne(
{ sku: "SKU-1001" },
{ $set: { name: "Desk Lamp", price: 29.99 } },
{ upsert: true }
);
if (result.upsertedId) {
console.log("New document created:", result.upsertedId);
}Hint
đ¨ 25. Error Handling
Robust applications must handle errors gracefully â including connection failures, validation errors, and duplicate key violations.
Handling duplicate key errors
try {
await collection.insertOne({ _id: 1, name: "Duplicate" });
await collection.insertOne({ _id: 1, name: "Duplicate" });
} catch (error) {
if (error.code === 11000) {
console.error("Duplicate key error:", error.message);
} else {
console.error("Unexpected error:", error);
}
}Example
đ 26. Best Practices
- Always use projection to fetch only the fields you actually need.
- Create appropriate indexes on frequently queried fields to improve performance.
- Prefer updateOne()/updateMany() over replaceOne() for partial updates.
- Use bulkWrite() for batch operations instead of looping individual calls.
- Always validate and sanitize user input before using it in a query filter.
- Use transactions when multiple operations must succeed or fail together.
â ī¸ 27. Common Mistakes
- Forgetting the filter in deleteMany() or updateMany(), accidentally affecting every document.
- Using replaceOne() when only a partial update (updateOne()) was intended, causing data loss.
- Not indexing fields used in frequent queries, leading to slow find() operations.
- Ignoring the result.matchedCount vs result.modifiedCount distinction when debugging updates.
Best Practice
â 28. Frequently Asked Questions
No. If you don't provide an _id field, MongoDB automatically generates a unique ObjectId for the document.
Yes. find() returns a cursor that lazily fetches documents in batches as you iterate, rather than loading the entire result set into memory at once.
No. The _id field is immutable once set. Attempting to modify it will result in an error.