CRUD Operations in MongoDB

Creating, Reading, Updating, and Deleting Data in MongoDB Collections

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.

graph TD CRUD[MongoDB CRUD Operations] --- C[Create] CRUD --- R[Read] CRUD --- U[Update] CRUD --- D[Delete] C --- CI[insertOne] C --- CM[insertMany] R --- RF[find] R --- RFO[findOne] R --- RC[countDocuments] U --- UO[updateOne] U --- UM[updateMany] U --- UR[replaceOne] D --- DO[deleteOne] D --- DM[deleteMany] style CRUD fill:#4DB33D,stroke:#333,stroke-width:2px,color:#fff

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);
          
graph TD Q[MongoDB Query Operators] --- C[Comparison] Q --- L[Logical] Q --- E[Element] Q --- A[Array] C --- CQ["$eq (equals)"] C --- CN["$ne (not equal)"] C --- CG["$gt (greater than)"] C --- CGE["$gte (greater than or equal)"] C --- CL["$lt (less than)"] C --- CLE["$lte (less than or equal)"] C --- CI["$in (in array)"] C --- CNI["$nin (not in array)"] L --- LA["$and"] L --- LO["$or"] L --- LN["$not"] L --- LNO["$nor"] E --- EE["$exists"] E --- ET["$type"] A --- AEL["$elemMatch"] A --- AAL["$all"] A --- AS["$size"] style Q fill:#4DB33D,stroke:#333,stroke-width:2px,color:#fff

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:

  1. Connect to MongoDB (local or Atlas)
  2. Create a collection called "students"
  3. Insert at least 5 student documents with fields like name, age, grade, courses (array), and address (embedded document)
  4. Query all students and display their information
  5. Update a student's information (e.g., change grade or add a course)
  6. Delete a student from the collection
  7. Find students that match specific criteria (e.g., in a particular grade or with specific courses)
  8. Close the MongoDB connection

Activity 2: Movie Database

Build a Node.js application for a movie database with the following features:

  1. Create collections for movies, actors, and directors
  2. Design appropriate document structures with embedded documents and references
  3. 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:

  1. Design collections for products, customers, and orders
  2. Create operations:
    • Add products with categories, prices, and inventory levels
    • Register customers with contact information and preferences
    • Create orders that reference customers and products
  3. Read operations:
    • Find products by category, price range, or availability
    • Retrieve a customer's order history
    • Get inventory reports
  4. Update operations:
    • Update product prices and inventory
    • Apply discounts to certain product categories
    • Update order status (e.g., from "pending" to "shipped")
  5. Delete operations:
    • Remove discontinued products
    • Allow customers to delete their accounts (with associated data)
    • Archive old orders

Key Takeaways

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.