MongoDB CRUD Overview
CRUD (Create, Read, Update, Delete) operations form the foundation of any database interaction. MongoDB provides a rich set of methods to perform these operations on collections and documents.
Analogy: Library Management
You can think of MongoDB CRUD operations like managing a library:
- Create is like adding new books to the library (insertOne, insertMany)
- Read is like searching for and browsing books (find, findOne)
- Update is like revising book information or moving books to different shelves (updateOne, updateMany)
- Delete is like removing books from the collection (deleteOne, deleteMany)
Just as a library has different ways to organize, find, and manage books, MongoDB provides various methods to interact with your data effectively.
Setting Up Node.js with MongoDB
Before we dive into CRUD operations, let's set up a Node.js project with the MongoDB driver.
Setting up a Node.js Project:
# Create a new directory for your project
mkdir mongodb-crud-demo
cd mongodb-crud-demo
# Initialize a Node.js project
npm init -y
# Install the MongoDB Node.js driver
npm install mongodb
Creating a Connection to MongoDB:
// connection.js
const { MongoClient } = require('mongodb');
// Connection URI
const uri = 'mongodb://localhost:27017';
// Create a new MongoClient
const client = new MongoClient(uri);
// Database Name
const dbName = 'crud_demo';
// Connect to MongoDB
async function connectToDatabase() {
try {
// Connect the client to the server
await client.connect();
console.log('Connected successfully to MongoDB server');
// Return the database object
const db = client.db(dbName);
return db;
} catch (error) {
console.error('Error connecting to MongoDB:', error);
throw error;
}
}
// For closing the connection when done
async function closeConnection() {
await client.close();
console.log('MongoDB connection closed');
}
module.exports = { connectToDatabase, closeConnection };
For MongoDB Atlas, you would use a connection string like this:
Connecting to MongoDB Atlas:
// Connection URI for MongoDB Atlas
const uri = 'mongodb+srv://username:password@cluster0.mongodb.net/crud_demo?retryWrites=true&w=majority';
Environment Variables for Connection
In a real-world application, you should store connection strings in environment variables:
// Install dotenv
npm install dotenv
// .env file
MONGODB_URI=mongodb://localhost:27017
DB_NAME=crud_demo
// connection.js
require('dotenv').config();
const uri = process.env.MONGODB_URI;
const dbName = process.env.DB_NAME;
Create Operations
Create operations add new documents to a collection. MongoDB provides two main methods for this: insertOne() and insertMany().
Inserting a Single Document:
// insertOne.js
const { connectToDatabase, closeConnection } = require('./connection');
async function insertOneDocument() {
try {
// Get database connection
const db = await connectToDatabase();
// Get the users collection
const collection = db.collection('users');
// Create a user document
const user = {
username: 'johndoe',
email: 'john@example.com',
profile: {
firstName: 'John',
lastName: 'Doe',
age: 30
},
interests: ['programming', 'reading', 'hiking'],
createdAt: new Date()
};
// Insert the document
const result = await collection.insertOne(user);
console.log(`Inserted document with _id: ${result.insertedId}`);
return result;
} catch (error) {
console.error('Error inserting document:', error);
throw error;
} finally {
await closeConnection();
}
}
insertOneDocument().catch(console.error);
Inserting Multiple Documents:
// insertMany.js
const { connectToDatabase, closeConnection } = require('./connection');
async function insertManyDocuments() {
try {
// Get database connection
const db = await connectToDatabase();
// Get the products collection
const collection = db.collection('products');
// Create an array of products
const products = [
{
name: 'Laptop',
price: 999.99,
category: 'Electronics',
inStock: true,
specs: {
cpu: 'Intel i7',
ram: '16GB',
storage: '512GB SSD'
},
tags: ['computers', 'work', 'high-performance']
},
{
name: 'Smartphone',
price: 699.99,
category: 'Electronics',
inStock: true,
specs: {
screen: '6.5 inch',
camera: '12MP',
storage: '128GB'
},
tags: ['mobile', 'communication']
},
{
name: 'Headphones',
price: 149.99,
category: 'Audio',
inStock: false,
specs: {
type: 'Over-ear',
wireless: true,
noiseCancellation: true
},
tags: ['audio', 'music', 'wireless']
}
];
// Insert the documents
const result = await collection.insertMany(products);
console.log(`${result.insertedCount} documents were inserted`);
console.log('Inserted document IDs:', result.insertedIds);
return result;
} catch (error) {
console.error('Error inserting documents:', error);
throw error;
} finally {
await closeConnection();
}
}
insertManyDocuments().catch(console.error);
Insert Options:
// insertWithOptions.js
const { connectToDatabase, closeConnection } = require('./connection');
async function insertWithOptions() {
try {
const db = await connectToDatabase();
const collection = db.collection('items');
// Document with custom _id
const item = {
_id: 'custom-id-123',
name: 'Special Item',
price: 199.99
};
// Insert with options
const result = await collection.insertOne(item, {
// Forces an acknowledgment that the write operation succeeded
writeConcern: { w: 'majority', wtimeout: 5000 },
// Specifies whether the document should be inserted if _id already exists
// Default is to throw an error if _id exists
ordered: true
});
console.log(`Inserted document with _id: ${result.insertedId}`);
// Try inserting documents with duplicate _id values
try {
const items = [
{ _id: 1, name: 'Item 1' },
{ _id: 2, name: 'Item 2' },
{ _id: 1, name: 'Duplicate ID' }, // This will cause an error with ordered: true
{ _id: 3, name: 'Item 3' }
];
// With ordered: true (default), insertion stops at the first error
const result1 = await collection.insertMany(items, { ordered: true });
console.log(`Inserted ${result1.insertedCount} documents`);
} catch (error) {
console.log('Error with ordered: true:', error.message);
// Documents before the error are inserted, but not after
const count = await collection.countDocuments();
console.log(`Collection now has ${count} documents`);
}
// Clean up for the next example
await collection.deleteMany({ _id: { $in: [1, 2, 3] } });
try {
const items = [
{ _id: 1, name: 'Item 1' },
{ _id: 2, name: 'Item 2' },
{ _id: 1, name: 'Duplicate ID' }, // This will cause an error
{ _id: 3, name: 'Item 3' }
];
// With ordered: false, MongoDB will try to insert all valid documents
const result2 = await collection.insertMany(items, { ordered: false });
console.log(`Inserted ${result2.insertedCount} documents`);
} catch (error) {
console.log('Error with ordered: false:', error.message);
// All documents except the duplicate are inserted
const count = await collection.countDocuments();
console.log(`Collection now has ${count} documents`);
}
} catch (error) {
console.error('Error in insertWithOptions:', error);
} finally {
await closeConnection();
}
}
insertWithOptions().catch(console.error);
Read Operations
Read operations retrieve documents from a collection. MongoDB provides several methods for querying data, with find() and findOne() being the most common.
Finding Documents:
// find.js
const { connectToDatabase, closeConnection } = require('./connection');
async function findDocuments() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Find all documents in the collection
console.log('\nAll products:');
const allProducts = await collection.find().toArray();
console.log(`Found ${allProducts.length} products`);
allProducts.forEach(product => console.log(`- ${product.name}: $${product.price}`));
// Find with a simple filter (exact match)
console.log('\nElectronics products:');
const electronics = await collection.find({ category: 'Electronics' }).toArray();
console.log(`Found ${electronics.length} electronics products`);
electronics.forEach(product => console.log(`- ${product.name}: $${product.price}`));
// Find with comparison operators
console.log('\nProducts under $200:');
const affordableProducts = await collection.find({ price: { $lt: 200 } }).toArray();
console.log(`Found ${affordableProducts.length} affordable products`);
affordableProducts.forEach(product => console.log(`- ${product.name}: $${product.price}`));
// Find with multiple conditions (AND)
console.log('\nIn-stock electronics:');
const inStockElectronics = await collection.find({
category: 'Electronics',
inStock: true
}).toArray();
console.log(`Found ${inStockElectronics.length} in-stock electronics`);
inStockElectronics.forEach(product => console.log(`- ${product.name}: $${product.price}`));
// Find with OR condition
console.log('\nElectronics or audio products:');
const techProducts = await collection.find({
$or: [
{ category: 'Electronics' },
{ category: 'Audio' }
]
}).toArray();
console.log(`Found ${techProducts.length} tech products`);
techProducts.forEach(product => console.log(`- ${product.name} (${product.category})`));
// Find with nested field
console.log('\nWireless audio products:');
const wirelessAudio = await collection.find({
category: 'Audio',
'specs.wireless': true
}).toArray();
console.log(`Found ${wirelessAudio.length} wireless audio products`);
wirelessAudio.forEach(product => console.log(`- ${product.name}`));
// Find with array contains
console.log('\nProducts tagged with "wireless":');
const wirelessProducts = await collection.find({
tags: 'wireless'
}).toArray();
console.log(`Found ${wirelessProducts.length} wireless-tagged products`);
wirelessProducts.forEach(product => console.log(`- ${product.name}`));
} catch (error) {
console.error('Error finding documents:', error);
} finally {
await closeConnection();
}
}
findDocuments().catch(console.error);
Finding a Single Document:
// findOne.js
const { connectToDatabase, closeConnection } = require('./connection');
async function findOneDocument() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Find one document by name
const laptop = await collection.findOne({ name: 'Laptop' });
if (laptop) {
console.log('Found laptop:');
console.log(`- Name: ${laptop.name}`);
console.log(`- Price: $${laptop.price}`);
console.log(`- Category: ${laptop.category}`);
console.log(`- Specs: ${JSON.stringify(laptop.specs)}`);
} else {
console.log('Laptop not found');
}
// Find one document with a condition that won't match
const expensiveLaptop = await collection.findOne({
name: 'Laptop',
price: { $gt: 1500 }
});
if (expensiveLaptop) {
console.log('\nFound expensive laptop');
} else {
console.log('\nNo expensive laptop found');
}
} catch (error) {
console.error('Error finding document:', error);
} finally {
await closeConnection();
}
}
findOneDocument().catch(console.error);
Cursor Methods and Projections:
// cursorMethods.js
const { connectToDatabase, closeConnection } = require('./connection');
async function cursorMethodsDemo() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Using limit - get only first 2 products
console.log('\nFirst 2 products:');
const limitedProducts = await collection.find().limit(2).toArray();
limitedProducts.forEach(product => console.log(`- ${product.name}`));
// Using skip - skip first 1 product (useful for pagination)
console.log('\nSkipping first product:');
const skippedProducts = await collection.find().skip(1).toArray();
skippedProducts.forEach(product => console.log(`- ${product.name}`));
// Using sort - sort by price (ascending)
console.log('\nProducts sorted by price (ascending):');
const sortedProducts = await collection.find().sort({ price: 1 }).toArray();
sortedProducts.forEach(product => console.log(`- ${product.name}: $${product.price}`));
// Using sort - sort by price (descending)
console.log('\nProducts sorted by price (descending):');
const expensiveFirst = await collection.find().sort({ price: -1 }).toArray();
expensiveFirst.forEach(product => console.log(`- ${product.name}: $${product.price}`));
// Using projection - only include name and price
console.log('\nProducts with only name and price:');
const nameAndPrice = await collection.find().project({ name: 1, price: 1, _id: 0 }).toArray();
console.log(nameAndPrice);
// Combining methods - skip 1, limit 2, sort by price (descending), include only name and price
console.log('\nCombining cursor methods:');
const combined = await collection.find()
.skip(1)
.limit(2)
.sort({ price: -1 })
.project({ name: 1, price: 1, _id: 0 })
.toArray();
console.log(combined);
// Count documents
const totalCount = await collection.countDocuments();
console.log(`\nTotal number of products: ${totalCount}`);
// Count documents with a filter
const electronicsCount = await collection.countDocuments({ category: 'Electronics' });
console.log(`Electronics products: ${electronicsCount}`);
// Using distinct
const categories = await collection.distinct('category');
console.log('\nDistinct categories:', categories);
} catch (error) {
console.error('Error in cursor methods demo:', error);
} finally {
await closeConnection();
}
}
cursorMethodsDemo().catch(console.error);
Common Query Patterns:
| Query Type | Example | Description |
|---|---|---|
| Exact Match | { status: "active" } |
Documents where status equals "active" |
| Multiple Criteria (AND) | { status: "active", age: 30 } |
Documents where status is "active" AND age is 30 |
| OR Condition | { $or: [{ status: "active" }, { age: { $gt: 30 } }] } |
Documents where status is "active" OR age is greater than 30 |
| Numeric Comparison | { age: { $gt: 25, $lt: 50 } } |
Documents where age is greater than 25 AND less than 50 |
| Field Exists | { phoneNumber: { $exists: true } } |
Documents that have a phoneNumber field |
| Array Contains | { tags: "mongodb" } |
Documents where tags array contains "mongodb" |
| Array Contains All | { tags: { $all: ["mongodb", "database"] } } |
Documents where tags contains both "mongodb" and "database" |
| Nested Field | { "address.city": "New York" } |
Documents where address.city equals "New York" |
| Element Match in Array | { comments: { $elemMatch: { author: "John", likes: { $gt: 5 } } } } |
Documents with a comment by "John" with more than 5 likes |
| Regular Expression | { name: { $regex: '^J', $options: 'i' } } |
Documents where name starts with "J" (case-insensitive) |
Update Operations
Update operations modify existing documents in a collection. MongoDB provides several methods for updating data: updateOne(), updateMany(), and replaceOne().
Updating a Single Document:
// updateOne.js
const { connectToDatabase, closeConnection } = require('./connection');
async function updateOneDocument() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// First, let's find the document before update
const laptopBefore = await collection.findOne({ name: 'Laptop' });
console.log('Before update:');
console.log(laptopBefore);
// Update a single document
const updateResult = await collection.updateOne(
{ name: 'Laptop' }, // filter
{
$set: { // update operator
price: 1099.99, // set new price
'specs.ram': '32GB', // update nested field
updatedAt: new Date() // add new field
}
}
);
console.log(`\nMatched ${updateResult.matchedCount} document(s)`);
console.log(`Modified ${updateResult.modifiedCount} document(s)`);
// Get the updated document
const laptopAfter = await collection.findOne({ name: 'Laptop' });
console.log('\nAfter update:');
console.log(laptopAfter);
} catch (error) {
console.error('Error updating document:', error);
} finally {
await closeConnection();
}
}
updateOneDocument().catch(console.error);
Updating Multiple Documents:
// updateMany.js
const { connectToDatabase, closeConnection } = require('./connection');
async function updateManyDocuments() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Update all electronics products
const updateResult = await collection.updateMany(
{ category: 'Electronics' }, // filter
{
$set: { onSale: true }, // add onSale field set to true
$mul: { price: 0.9 } // multiply price by 0.9 (10% discount)
}
);
console.log(`Matched ${updateResult.matchedCount} document(s)`);
console.log(`Modified ${updateResult.modifiedCount} document(s)`);
// Get the updated documents
const discountedProducts = await collection.find({ onSale: true }).toArray();
console.log('\nDiscounted products:');
discountedProducts.forEach(product => {
console.log(`- ${product.name}: $${product.price.toFixed(2)} (Category: ${product.category})`);
});
} catch (error) {
console.error('Error updating documents:', error);
} finally {
await closeConnection();
}
}
updateManyDocuments().catch(console.error);
Common Update Operators:
// updateOperators.js
const { connectToDatabase, closeConnection } = require('./connection');
async function updateOperatorsDemo() {
try {
const db = await connectToDatabase();
// Create a test collection and insert a document
const collection = db.collection('updateDemo');
await collection.drop().catch(() => {}); // Drop if exists
await collection.insertOne({
name: 'Test Product',
price: 100,
quantity: 10,
categories: ['A', 'B'],
metadata: {
created: new Date(),
views: 5
}
});
// Display initial document
console.log('Initial document:');
console.log(await collection.findOne({}));
// $set - Set field values
await collection.updateOne(
{ name: 'Test Product' },
{ $set: { description: 'A test product for demonstration' } }
);
console.log('\nAfter $set:');
console.log(await collection.findOne({}));
// $inc - Increment field values
await collection.updateOne(
{ name: 'Test Product' },
{ $inc: { price: 20, quantity: -2, 'metadata.views': 3 } }
);
console.log('\nAfter $inc:');
console.log(await collection.findOne({}));
// $mul - Multiply field values
await collection.updateOne(
{ name: 'Test Product' },
{ $mul: { price: 1.1 } } // Increase price by 10%
);
console.log('\nAfter $mul:');
console.log(await collection.findOne({}));
// $rename - Rename fields
await collection.updateOne(
{ name: 'Test Product' },
{ $rename: { 'metadata.views': 'metadata.viewCount' } }
);
console.log('\nAfter $rename:');
console.log(await collection.findOne({}));
// $unset - Remove fields
await collection.updateOne(
{ name: 'Test Product' },
{ $unset: { description: '' } }
);
console.log('\nAfter $unset:');
console.log(await collection.findOne({}));
// $push - Add an element to an array
await collection.updateOne(
{ name: 'Test Product' },
{ $push: { categories: 'C' } }
);
console.log('\nAfter $push:');
console.log(await collection.findOne({}));
// $pull - Remove elements from an array
await collection.updateOne(
{ name: 'Test Product' },
{ $pull: { categories: 'B' } }
);
console.log('\nAfter $pull:');
console.log(await collection.findOne({}));
// $addToSet - Add elements to an array only if they don't exist
await collection.updateOne(
{ name: 'Test Product' },
{ $addToSet: { categories: 'C' } } // C already exists, won't be added again
);
await collection.updateOne(
{ name: 'Test Product' },
{ $addToSet: { categories: 'D' } } // D doesn't exist, will be added
);
console.log('\nAfter $addToSet:');
console.log(await collection.findOne({}));
// $setOnInsert - Set field values only if inserting
await collection.updateOne(
{ name: 'New Product' }, // Doesn't exist, will be inserted
{
$set: { price: 50 },
$setOnInsert: { createdAt: new Date() }
},
{ upsert: true } // Create if doesn't exist
);
console.log('\nAfter $setOnInsert:');
console.log(await collection.findOne({ name: 'New Product' }));
// Update the same document again
await collection.updateOne(
{ name: 'New Product' },
{
$set: { price: 60 },
$setOnInsert: { createdAt: new Date(2000, 0, 1) } // Won't be set this time
},
{ upsert: true }
);
console.log('\nAfter second update with $setOnInsert:');
console.log(await collection.findOne({ name: 'New Product' }));
} catch (error) {
console.error('Error in update operators demo:', error);
} finally {
await closeConnection();
}
}
updateOperatorsDemo().catch(console.error);
Replacing a Document:
// replaceOne.js
const { connectToDatabase, closeConnection } = require('./connection');
async function replaceOneDocument() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// First, let's find the document before replacement
const smartphoneBefore = await collection.findOne({ name: 'Smartphone' });
console.log('Before replacement:');
console.log(smartphoneBefore);
// Replace the entire document
const replaceResult = await collection.replaceOne(
{ name: 'Smartphone' },
{
name: 'Smartphone Pro',
price: 899.99,
category: 'Electronics',
inStock: true,
specs: {
screen: '6.7 inch OLED',
camera: '48MP',
storage: '256GB',
battery: '4500mAh'
},
colors: ['black', 'silver', 'gold'],
releaseDate: new Date('2023-09-15')
}
);
console.log(`\nMatched ${replaceResult.matchedCount} document(s)`);
console.log(`Modified ${replaceResult.modifiedCount} document(s)`);
// Get the replaced document
const smartphoneAfter = await collection.findOne({ name: 'Smartphone Pro' });
console.log('\nAfter replacement:');
console.log(smartphoneAfter);
// Note: The original _id is preserved in a replacement
console.log('\nOriginal _id preserved:', smartphoneBefore._id.equals(smartphoneAfter._id));
} catch (error) {
console.error('Error replacing document:', error);
} finally {
await closeConnection();
}
}
replaceOneDocument().catch(console.error);
Upsert: Update or Insert:
// upsert.js
const { connectToDatabase, closeConnection } = require('./connection');
async function upsertDemo() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Try to update a document that doesn't exist
console.log('\nUpsert Example 1: Target doesn\'t exist yet');
const upsertResult1 = await collection.updateOne(
{ name: 'Tablet Pro' }, // This doesn't exist yet
{
$set: {
price: 549.99,
category: 'Electronics',
inStock: true,
updatedAt: new Date()
}
},
{ upsert: true } // Create if it doesn't exist
);
console.log(`Matched ${upsertResult1.matchedCount} existing document(s)`);
console.log(`Modified ${upsertResult1.modifiedCount} document(s)`);
console.log(`Upserted ID: ${upsertResult1.upsertedId?._id}`);
// Find the upserted document
const tablet = await collection.findOne({ name: 'Tablet Pro' });
console.log('\nUpserted document:');
console.log(tablet);
// Update a document that now exists
console.log('\nUpsert Example 2: Target now exists');
const upsertResult2 = await collection.updateOne(
{ name: 'Tablet Pro' }, // Now this exists
{
$set: {
price: 599.99, // Update the price
specs: { screen: '11 inch' },
updatedAt: new Date()
}
},
{ upsert: true } // Won't need to create a new document
);
console.log(`Matched ${upsertResult2.matchedCount} existing document(s)`);
console.log(`Modified ${upsertResult2.modifiedCount} document(s)`);
console.log(`Upserted ID: ${upsertResult2.upsertedId?._id || 'None (document was updated)'}`);
// Find the updated document
const updatedTablet = await collection.findOne({ name: 'Tablet Pro' });
console.log('\nUpdated document:');
console.log(updatedTablet);
} catch (error) {
console.error('Error in upsert demo:', error);
} finally {
await closeConnection();
}
}
upsertDemo().catch(console.error);
Common Update Operators:
| Operator | Description | Example |
|---|---|---|
$set |
Sets the value of a field | { $set: { status: "active" } } |
$unset |
Removes a field | { $unset: { status: "" } } |
$inc |
Increments a field by a specified value | { $inc: { quantity: -1, views: 1 } } |
$mul |
Multiplies a field by a specified value | { $mul: { price: 0.9 } } |
$rename |
Renames a field | { $rename: { "name": "title" } } |
$push |
Adds an element to an array | { $push: { tags: "new" } } |
$pull |
Removes elements from an array that match a condition | { $pull: { tags: "old" } } |
$addToSet |
Adds elements to an array only if they don't exist | { $addToSet: { tags: "unique" } } |
$pop |
Removes the first or last element of an array | { $pop: { tags: 1 } } (last) |
$setOnInsert |
Sets field values only when an upsert creates a document | { $setOnInsert: { created: new Date() } } |
Delete Operations
Delete operations remove documents from a collection. MongoDB provides deleteOne() and deleteMany() methods for this purpose.
Deleting a Single Document:
// deleteOne.js
const { connectToDatabase, closeConnection } = require('./connection');
async function deleteOneDocument() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Find all products before deletion
const productsBefore = await collection.find().toArray();
console.log(`Before deletion: ${productsBefore.length} products`);
// Delete a single document
const deleteResult = await collection.deleteOne({ name: 'Headphones' });
console.log(`\nDeleted ${deleteResult.deletedCount} document(s)`);
// Find all products after deletion
const productsAfter = await collection.find().toArray();
console.log(`After deletion: ${productsAfter.length} products`);
// List remaining products
console.log('\nRemaining products:');
productsAfter.forEach(product => console.log(`- ${product.name}`));
} catch (error) {
console.error('Error deleting document:', error);
} finally {
await closeConnection();
}
}
deleteOneDocument().catch(console.error);
Deleting Multiple Documents:
// deleteMany.js
const { connectToDatabase, closeConnection } = require('./connection');
async function deleteManyDocuments() {
try {
const db = await connectToDatabase();
const collection = db.collection('products');
// Count products by category before deletion
const categoriesBefore = await collection.aggregate([
{ $group: { _id: '$category', count: { $sum: 1 } } }
]).toArray();
console.log('Categories before deletion:');
categoriesBefore.forEach(cat => console.log(`- ${cat._id}: ${cat.count} product(s)`));
// Delete all products that are out of stock
const deleteResult = await collection.deleteMany({ inStock: false });
console.log(`\nDeleted ${deleteResult.deletedCount} document(s)`);
// Count products by category after deletion
const categoriesAfter = await collection.aggregate([
{ $group: { _id: '$category', count: { $sum: 1 } } }
]).toArray();
console.log('\nCategories after deletion:');
categoriesAfter.forEach(cat => console.log(`- ${cat._id}: ${cat.count} product(s)`));
// List all remaining products
const remainingProducts = await collection.find().toArray();
console.log('\nRemaining products:');
remainingProducts.forEach(product => console.log(`- ${product.name} (${product.category})`));
} catch (error) {
console.error('Error deleting documents:', error);
} finally {
await closeConnection();
}
}
deleteManyDocuments().catch(console.error);
Dropping a Collection:
// dropCollection.js
const { connectToDatabase, closeConnection } = require('./connection');
async function dropCollectionDemo() {
try {
const db = await connectToDatabase();
// List collections before
const collectionsBefore = await db.listCollections().toArray();
console.log('Collections before:');
collectionsBefore.forEach(collection => console.log(`- ${collection.name}`));
// Create a temporary collection
await db.createCollection('temporary');
console.log('\nCreated "temporary" collection');
// Insert a document
await db.collection('temporary').insertOne({ name: 'Test Document' });
// List collections after creation
const collectionsAfter = await db.listCollections().toArray();
console.log('\nCollections after creation:');
collectionsAfter.forEach(collection => console.log(`- ${collection.name}`));
// Drop the collection
await db.collection('temporary').drop();
console.log('\nDropped "temporary" collection');
// List collections after dropping
const collectionsAfterDrop = await db.listCollections().toArray();
console.log('\nCollections after dropping:');
collectionsAfterDrop.forEach(collection => console.log(`- ${collection.name}`));
} catch (error) {
console.error('Error in drop collection demo:', error);
} finally {
await closeConnection();
}
}
dropCollectionDemo().catch(console.error);
Combining CRUD Operations
Let's put together everything we've learned to create a simple product inventory manager.
Product Inventory Manager:
// inventoryManager.js
const { MongoClient, ObjectId } = require('mongodb');
class InventoryManager {
constructor(uri, dbName) {
this.uri = uri;
this.dbName = dbName;
this.client = new MongoClient(uri);
this.db = null;
this.collection = null;
}
async connect() {
try {
await this.client.connect();
this.db = this.client.db(this.dbName);
this.collection = this.db.collection('products');
console.log('Connected to MongoDB and initialized inventory manager');
} catch (error) {
console.error('Error connecting to MongoDB:', error);
throw error;
}
}
async close() {
await this.client.close();
console.log('Closed MongoDB connection');
}
// Create operations
async addProduct(product) {
try {
// Ensure createdAt is set
product.createdAt = new Date();
const result = await this.collection.insertOne(product);
console.log(`Added product with ID: ${result.insertedId}`);
return result;
} catch (error) {
console.error('Error adding product:', error);
throw error;
}
}
async addMultipleProducts(products) {
try {
// Ensure createdAt is set for all products
const productsWithTimestamp = products.map(product => ({
...product,
createdAt: new Date()
}));
const result = await this.collection.insertMany(productsWithTimestamp);
console.log(`Added ${result.insertedCount} products`);
return result;
} catch (error) {
console.error('Error adding multiple products:', error);
throw error;
}
}
// Read operations
async getAllProducts() {
try {
return await this.collection.find().toArray();
} catch (error) {
console.error('Error getting all products:', error);
throw error;
}
}
async getProductById(id) {
try {
return await this.collection.findOne({ _id: new ObjectId(id) });
} catch (error) {
console.error('Error getting product by ID:', error);
throw error;
}
}
async getProductsByCategory(category) {
try {
return await this.collection.find({ category }).toArray();
} catch (error) {
console.error('Error getting products by category:', error);
throw error;
}
}
async getProductsInStock() {
try {
return await this.collection.find({ inStock: true }).toArray();
} catch (error) {
console.error('Error getting in-stock products:', error);
throw error;
}
}
// Update operations
async updateProduct(id, updates) {
try {
// Add updatedAt timestamp
updates.updatedAt = new Date();
const result = await this.collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: updates }
);
console.log(`Updated product: matched=${result.matchedCount}, modified=${result.modifiedCount}`);
return result;
} catch (error) {
console.error('Error updating product:', error);
throw error;
}
}
async adjustInventory(id, quantity) {
try {
const result = await this.collection.updateOne(
{ _id: new ObjectId(id) },
{
$inc: { stock: quantity },
$set: { updatedAt: new Date() }
}
);
// If stock is reduced to 0 or below, mark as out of stock
if (quantity < 0) {
const product = await this.getProductById(id);
if (product && product.stock <= 0) {
await this.collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: { inStock: false } }
);
console.log(`Product ${id} marked as out of stock`);
}
} else if (quantity > 0) {
// If adding stock, ensure inStock is true
await this.collection.updateOne(
{ _id: new ObjectId(id) },
{ $set: { inStock: true } }
);
}
return result;
} catch (error) {
console.error('Error adjusting inventory:', error);
throw error;
}
}
async applyDiscount(category, percentage) {
try {
const multiplier = 1 - (percentage / 100);
const result = await this.collection.updateMany(
{ category },
{
$mul: { price: multiplier },
$set: {
onSale: true,
discountPercentage: percentage,
updatedAt: new Date()
}
}
);
console.log(`Applied ${percentage}% discount to ${result.modifiedCount} products in ${category} category`);
return result;
} catch (error) {
console.error('Error applying discount:', error);
throw error;
}
}
// Delete operations
async removeProduct(id) {
try {
const result = await this.collection.deleteOne({ _id: new ObjectId(id) });
console.log(`Removed ${result.deletedCount} product(s)`);
return result;
} catch (error) {
console.error('Error removing product:', error);
throw error;
}
}
async clearCategory(category) {
try {
const result = await this.collection.deleteMany({ category });
console.log(`Cleared ${result.deletedCount} product(s) from ${category} category`);
return result;
} catch (error) {
console.error('Error clearing category:', error);
throw error;
}
}
// Statistics and reports
async getCategorySummary() {
try {
return await this.collection.aggregate([
{
$group: {
_id: '$category',
count: { $sum: 1 },
averagePrice: { $avg: '$price' },
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' },
totalStock: { $sum: '$stock' }
}
},
{ $sort: { count: -1 } }
]).toArray();
} catch (error) {
console.error('Error getting category summary:', error);
throw error;
}
}
async getLowStockProducts(threshold = 5) {
try {
return await this.collection.find({
stock: { $lte: threshold },
inStock: true
}).toArray();
} catch (error) {
console.error('Error getting low stock products:', error);
throw error;
}
}
}
// Example usage
async function inventoryDemo() {
const inventoryManager = new InventoryManager('mongodb://localhost:27017', 'inventory_demo');
try {
await inventoryManager.connect();
// Create a collection for our demo
try {
await inventoryManager.db.createCollection('products');
} catch (error) {
// Collection may already exist
}
// Clear previous data
await inventoryManager.collection.deleteMany({});
// Add products
await inventoryManager.addMultipleProducts([
{
name: 'Laptop Pro',
price: 1299.99,
category: 'Electronics',
stock: 10,
inStock: true,
specs: {
cpu: 'Intel i7',
ram: '16GB',
storage: '512GB SSD'
}
},
{
name: 'Smartphone XL',
price: 899.99,
category: 'Electronics',
stock: 15,
inStock: true,
specs: {
screen: '6.5 inch',
camera: '12MP',
storage: '128GB'
}
},
{
name: 'Coffee Maker',
price: 79.99,
category: 'Kitchen',
stock: 8,
inStock: true
},
{
name: 'Bluetooth Speaker',
price: 49.99,
category: 'Electronics',
stock: 20,
inStock: true
},
{
name: 'Blender',
price: 59.99,
category: 'Kitchen',
stock: 5,
inStock: true
}
]);
// Get all products
console.log('\nAll Products:');
const allProducts = await inventoryManager.getAllProducts();
allProducts.forEach(product => {
console.log(`- ${product.name}: $${product.price} (${product.category}), Stock: ${product.stock}`);
});
// Get products by category
console.log('\nElectronics Products:');
const electronics = await inventoryManager.getProductsByCategory('Electronics');
electronics.forEach(product => {
console.log(`- ${product.name}: $${product.price}, Stock: ${product.stock}`);
});
// Update a product
const laptopId = allProducts.find(p => p.name === 'Laptop Pro')._id;
await inventoryManager.updateProduct(laptopId, {
price: 1399.99,
specs: {
cpu: 'Intel i9',
ram: '32GB',
storage: '1TB SSD'
}
});
// Apply discount to a category
await inventoryManager.applyDiscount('Kitchen', 15);
// Adjust inventory
const speakerId = allProducts.find(p => p.name === 'Bluetooth Speaker')._id;
await inventoryManager.adjustInventory(speakerId, -18); // Sell 18 units
// Get updated products
console.log('\nUpdated Products:');
const updatedProducts = await inventoryManager.getAllProducts();
updatedProducts.forEach(product => {
console.log(`- ${product.name}: $${product.price.toFixed(2)} (${product.category}), Stock: ${product.stock}`);
if (product.onSale) {
console.log(` On sale: ${product.discountPercentage}% off`);
}
if (product.updatedAt) {
console.log(` Last updated: ${product.updatedAt}`);
}
});
// Get category summary
console.log('\nCategory Summary:');
const categorySummary = await inventoryManager.getCategorySummary();
categorySummary.forEach(category => {
console.log(`- ${category._id}:`);
console.log(` Products: ${category.count}`);
console.log(` Average Price: $${category.averagePrice.toFixed(2)}`);
console.log(` Price Range: $${category.minPrice.toFixed(2)} - $${category.maxPrice.toFixed(2)}`);
console.log(` Total Stock: ${category.totalStock}`);
});
// Get low stock products
console.log('\nLow Stock Products:');
const lowStock = await inventoryManager.getLowStockProducts();
if (lowStock.length === 0) {
console.log('No products with low stock');
} else {
lowStock.forEach(product => {
console.log(`- ${product.name}: Only ${product.stock} units left`);
});
}
// Remove a product
const blenderId = allProducts.find(p => p.name === 'Blender')._id;
await inventoryManager.removeProduct(blenderId);
// Final product list
console.log('\nFinal Product List:');
const finalProducts = await inventoryManager.getAllProducts();
finalProducts.forEach(product => {
console.log(`- ${product.name}: $${product.price.toFixed(2)} (${product.category}), Stock: ${product.stock}`);
});
} catch (error) {
console.error('Error in inventory demo:', error);
} finally {
await inventoryManager.close();
}
}
inventoryDemo().catch(console.error);
Practical Activities
Activity 1: Basic CRUD Operations
Create a Node.js script that performs the following operations:
- Connect to MongoDB (local or Atlas)
- Create a collection called "students"
- Insert at least 5 student documents with fields like name, age, grade, courses (array), and address (embedded document)
- Query all students and display their information
- Update a student's information (e.g., change grade or add a course)
- Delete a student from the collection
- Find students that match specific criteria (e.g., in a particular grade or with specific courses)
- Close the MongoDB connection
Activity 2: Movie Database
Build a Node.js application for a movie database with the following features:
- Create collections for movies, actors, and directors
- Design appropriate document structures with embedded documents and references
- Implement functions to:
- Add new movies with details like title, year, genre, rating, etc.
- Add actors and directors with their filmography
- Find movies by title, genre, or release year
- Update movie ratings and other details
- Delete movies from the database
- Generate reports like "highest-rated movies" or "most prolific directors"
Activity 3: E-commerce Data Operations
Implement a set of MongoDB CRUD operations for an e-commerce application:
- Design collections for products, customers, and orders
- Create operations:
- Add products with categories, prices, and inventory levels
- Register customers with contact information and preferences
- Create orders that reference customers and products
- Read operations:
- Find products by category, price range, or availability
- Retrieve a customer's order history
- Get inventory reports
- Update operations:
- Update product prices and inventory
- Apply discounts to certain product categories
- Update order status (e.g., from "pending" to "shipped")
- Delete operations:
- Remove discontinued products
- Allow customers to delete their accounts (with associated data)
- Archive old orders
Key Takeaways
- MongoDB provides a comprehensive set of CRUD operations for working with documents and collections
- The Node.js MongoDB driver makes it easy to interact with MongoDB from JavaScript applications
- Create operations like
insertOne()andinsertMany()add new documents to collections - Read operations like
find()andfindOne()retrieve documents with flexible query conditions - Update operations like
updateOne()andupdateMany()modify existing documents - Delete operations like
deleteOne()anddeleteMany()remove documents from collections - MongoDB provides a rich set of query and update operators for complex operations
- Combining CRUD operations allows you to build powerful data management applications
Next Steps
In our next lecture, we'll explore the MongoDB Aggregation Framework and how to use Mongoose, an Object Data Modeling (ODM) library for MongoDB and Node.js.