Update Data into MongoDB Using Node.js: Quick & Easy Guide - Techvblogs

Update Data into MongoDB Using Node.js: Quick & Easy Guide

Learn how to update data into MongoDB using Node.js with this simple guide.


Suresh Ramani - Author - Techvblogs
Suresh Ramani
 

1 day ago

TechvBlogs - Google News

Updating data in MongoDB using Node.js is a fundamental skill every backend developer needs to master for building dynamic, data-driven applications. Whether you’re modifying user profiles, updating product inventory, or making bulk changes to your database, understanding MongoDB’s update operations will enable you to create responsive applications that keep your data current and accurate.

This comprehensive guide walks you through everything from basic single-document updates to complex bulk operations, complete with practical examples and best practices that will help you avoid common pitfalls and write efficient, secure update code.

What Is MongoDB and Why Use It with Node.js

Explaining MongoDB’s NoSQL nature and document structure

MongoDB is a document-oriented NoSQL database that stores data in flexible, JSON-like documents called BSON (Binary JSON). Unlike traditional relational databases with rigid table structures, MongoDB allows you to store and modify data in a more natural, object-oriented format that closely mirrors how developers work with data in their applications.

Each document in MongoDB can contain:

  • Simple fields: Strings, numbers, booleans, and dates
  • Nested objects: Complex data structures within documents
  • Arrays: Lists of values or embedded documents
  • Mixed data types: Different field types within the same collection

This flexibility makes MongoDB particularly well-suited for applications where data structures evolve over time or where you need to store varying document formats within the same collection.

Benefits of combining MongoDB with Node.js for backend development

The pairing of MongoDB and Node.js creates a powerful development stack that offers several compelling advantages for modern web applications:

Native JSON Support: Both MongoDB and Node.js work seamlessly with JSON, eliminating the need for complex object-relational mapping and reducing data transformation overhead during update operations.

Flexible Schema Evolution: MongoDB’s schema-less design combined with Node.js’s dynamic nature allows you to modify data structures and update operations without complex database migrations.

High Performance: MongoDB’s document-based structure and Node.js’s non-blocking I/O model work together to deliver excellent performance for both read and write operations, including complex updates.

Rapid Development: The similarity between JavaScript objects and MongoDB documents means you can update data using familiar syntax and patterns, accelerating development cycles.

Scalability: Both technologies are designed with horizontal scaling in mind, making it easier to handle increasing update loads as your application grows.

Setting Up Your Environment for MongoDB and Node.js

Installing Node.js and MongoDB locally or via cloud services

Before you can start updating data in MongoDB, you need a properly configured development environment. You have two main options: local installation or cloud-based setup.

Local Installation Approach:

For development purposes, installing both Node.js and MongoDB locally provides the fastest performance and complete control over your environment.

Node.js Installation:

# Windows (using Chocolatey)
choco install nodejs

# macOS (using Homebrew)
brew install node

# Ubuntu/Debian
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt-get install -y nodejs

# Verify installation
node --version
npm --version

MongoDB Local Installation:

# Windows (using Chocolatey)
choco install mongodb

# macOS (using Homebrew)
brew tap mongodb/brew
brew install mongodb-community

# Ubuntu/Debian
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org

# Start MongoDB service
sudo systemctl start mongod
sudo systemctl enable mongod

Using MongoDB Atlas for quick cloud setup

MongoDB Atlas offers a cloud-hosted solution that’s perfect for development and production environments without the overhead of managing database infrastructure.

Setting up MongoDB Atlas:

  1. Create an account at MongoDB Atlas
  2. Build a new cluster (choose the free M0 tier for development)
  3. Configure database access by creating a database user with appropriate permissions
  4. Set up network access by adding your IP address to the allowlist
  5. Get your connection string from the cluster dashboard

Your Atlas connection string will look like this:

mongodb+srv://username:[email protected]/mydatabase?retryWrites=true&w=majority

Installing required npm packages like mongodb or mongoose

Create a new Node.js project and install the necessary MongoDB drivers:

# Create project directory
mkdir mongodb-update-tutorial
cd mongodb-update-tutorial

# Initialize Node.js project
npm init -y

# Install MongoDB native driver
npm install mongodb

# Optional: Install Mongoose for schema-based operations
npm install mongoose

# Install additional helpful packages
npm install dotenv  # For environment variable management

Your package.json should now include:

{
  "dependencies": {
    "mongodb": "^6.3.0",
    "mongoose": "^8.0.3",
    "dotenv": "^16.3.1"
  }
}

Create a .env file to store your database connection string:

MONGODB_URI=mongodb://localhost:27017/updatedb
# Or for Atlas:
# MONGODB_URI=mongodb+srv://username:[email protected]/updatedb

Connecting Node.js to MongoDB: Establishing the Bridge

Creating a connection using MongoDB native driver

Establishing a robust connection to MongoDB is the foundation for all update operations. The MongoDB native driver provides efficient connection pooling and automatic reconnection handling.

const { MongoClient } = require('mongodb');
require('dotenv').config();

class DatabaseConnection {
    constructor() {
        this.client = null;
        this.db = null;
    }
    
    async connect() {
        try {
            const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/updatedb';
            
            this.client = new MongoClient(uri, {
                maxPoolSize: 10,          // Maximum number of connections
                serverSelectionTimeoutMS: 5000, // Timeout for server selection
                socketTimeoutMS: 45000,   // Timeout for socket operations
                bufferMaxEntries: 0       // Disable mongoose buffering
            });
            
            await this.client.connect();
            this.db = this.client.db();
            
            // Test the connection
            await this.db.admin().ping();
            console.log('Successfully connected to MongoDB!');
            
            return this.db;
        } catch (error) {
            console.error('Failed to connect to MongoDB:', error);
            throw error;
        }
    }
    
    async disconnect() {
        if (this.client) {
            await this.client.close();
            console.log('Disconnected from MongoDB');
        }
    }
    
    getDatabase() {
        if (!this.db) {
            throw new Error('Database connection not established. Call connect() first.');
        }
        return this.db;
    }
}

// Create a singleton instance
const dbConnection = new DatabaseConnection();

module.exports = dbConnection;

Error handling and connection options for production-ready apps

Production applications require robust error handling and connection management to ensure reliability and performance:

const { MongoClient } = require('mongodb');

class ProductionDatabaseManager {
    constructor() {
        this.client = null;
        this.db = null;
        this.isConnected = false;
        this.connectionAttempts = 0;
        this.maxRetryAttempts = 5;
    }
    
    async connect() {
        const uri = process.env.MONGODB_URI;
        
        if (!uri) {
            throw new Error('MONGODB_URI environment variable is required');
        }
        
        const options = {
            // Connection pool settings
            maxPoolSize: 20,
            minPoolSize: 5,
            maxIdleTimeMS: 30000,
            
            // Timeout settings
            serverSelectionTimeoutMS: 10000,
            socketTimeoutMS: 60000,
            connectTimeoutMS: 10000,
            
            // Retry and failover settings
            retryWrites: true,
            retryReads: true,
            
            // Compression
            compressors: ['zlib'],
            
            // Monitoring
            monitorCommands: process.env.NODE_ENV === 'development'
        };
        
        while (this.connectionAttempts < this.maxRetryAttempts) {
            try {
                this.client = new MongoClient(uri, options);
                await this.client.connect();
                this.db = this.client.db();
                
                // Verify connection
                await this.db.admin().ping();
                
                this.isConnected = true;
                this.connectionAttempts = 0;
                
                console.log(`Connected to MongoDB (attempt ${this.connectionAttempts + 1})`);
                
                // Set up connection event listeners
                this.setupEventListeners();
                
                return this.db;
                
            } catch (error) {
                this.connectionAttempts++;
                console.error(`Connection attempt ${this.connectionAttempts} failed:`, error.message);
                
                if (this.connectionAttempts >= this.maxRetryAttempts) {
                    throw new Error(`Failed to connect after ${this.maxRetryAttempts} attempts`);
                }
                
                // Wait before retrying (exponential backoff)
                const delay = Math.min(1000 * Math.pow(2, this.connectionAttempts), 30000);
                console.log(`Retrying in ${delay}ms...`);
                await new Promise(resolve => setTimeout(resolve, delay));
            }
        }
    }
    
    setupEventListeners() {
        if (!this.client) return;
        
        this.client.on('serverHeartbeatSucceeded', () => {
            if (!this.isConnected) {
                console.log('MongoDB connection restored');
                this.isConnected = true;
            }
        });
        
        this.client.on('serverHeartbeatFailed', (event) => {
            console.warn('MongoDB heartbeat failed:', event.failure.message);
            this.isConnected = false;
        });
        
        this.client.on('topologyClosing', () => {
            console.log('MongoDB topology is closing');
            this.isConnected = false;
        });
    }
    
    async ensureConnection() {
        if (!this.isConnected) {
            console.log('Connection lost, attempting to reconnect...');
            await this.connect();
        }
    }
    
    async executeWithRetry(operation, maxRetries = 3) {
        let lastError;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                await this.ensureConnection();
                return await operation(this.db);
            } catch (error) {
                lastError = error;
                console.error(`Operation attempt ${attempt} failed:`, error.message);
                
                if (attempt < maxRetries) {
                    const delay = 1000 * attempt;
                    await new Promise(resolve => setTimeout(resolve, delay));
                }
            }
        }
        
        throw lastError;
    }
}

// Usage example
const dbManager = new ProductionDatabaseManager();

module.exports = dbManager;

Different Methods to Update Data in MongoDB

Using updateOne() and updateMany() to target documents

MongoDB provides several methods for updating documents, each designed for specific use cases. The updateOne() and updateMany() methods are the most commonly used for targeted updates.

updateOne() Method:

The updateOne() method updates the first document that matches your filter criteria. It’s perfect when you need to modify a specific record, such as updating a user’s profile information.

const { ObjectId } = require('mongodb');

async function updateUserEmail() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const usersCollection = db.collection('users');
        
        // Update a specific user's email address
        const filter = { _id: new ObjectId('507f1f77bcf86cd799439011') };
        const updateDoc = {
            $set: {
                email: '[email protected]',
                updatedAt: new Date()
            }
        };
        
        const result = await usersCollection.updateOne(filter, updateDoc);
        
        console.log(`Matched ${result.matchedCount} document(s)`);
        console.log(`Modified ${result.modifiedCount} document(s)`);
        
        if (result.modifiedCount === 1) {
            console.log('User email updated successfully');
        } else if (result.matchedCount === 0) {
            console.log('No user found with the specified ID');
        } else {
            console.log('User found but no changes were made');
        }
        
        return result;
        
    } catch (error) {
        console.error('Error updating user email:', error);
        throw error;
    }
}

// Alternative: Update by email address
async function updateUserByEmail(oldEmail, newEmail) {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const usersCollection = db.collection('users');
        
        const filter = { email: oldEmail };
        const updateDoc = {
            $set: {
                email: newEmail,
                updatedAt: new Date()
            },
            $inc: {
                updateCount: 1  // Increment update counter
            }
        };
        
        const options = {
            upsert: false,  // Don't create if doesn't exist
            returnDocument: 'after'  // Return updated document
        };
        
        const result = await usersCollection.updateOne(filter, updateDoc, options);
        return result;
        
    } catch (error) {
        console.error('Error updating user by email:', error);
        throw error;
    }
}

updateMany() Method:

The updateMany() method updates all documents that match your filter criteria. This is ideal for bulk operations like updating prices, statuses, or applying organization-wide changes.

async function updateProductPrices() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const productsCollection = db.collection('products');
        
        // Apply 10% discount to all electronics products
        const filter = { 
            category: 'Electronics',
            price: { $gt: 0 }  // Only products with positive prices
        };
        
        const updateDoc = {
            $mul: {
                price: 0.9  // Multiply price by 0.9 (10% discount)
            },
            $set: {
                discountApplied: true,
                discountDate: new Date(),
                updatedAt: new Date()
            }
        };
        
        const result = await productsCollection.updateMany(filter, updateDoc);
        
        console.log(`Matched ${result.matchedCount} products`);
        console.log(`Modified ${result.modifiedCount} products`);
        
        return {
            success: true,
            matchedCount: result.matchedCount,
            modifiedCount: result.modifiedCount,
            message: `Applied discount to ${result.modifiedCount} electronics products`
        };
        
    } catch (error) {
        console.error('Error updating product prices:', error);
        return {
            success: false,
            message: error.message
        };
    }
}

// Bulk status update example
async function activateInactiveUsers() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const usersCollection = db.collection('users');
        
        // Activate users who haven't logged in for 30 days but are still verified
        const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
        
        const filter = {
            status: 'inactive',
            emailVerified: true,
            lastLoginAt: { $gte: thirtyDaysAgo }
        };
        
        const updateDoc = {
            $set: {
                status: 'active',
                reactivatedAt: new Date(),
                updatedAt: new Date()
            },
            $unset: {
                inactiveReason: ""  // Remove inactive reason field
            }
        };
        
        const result = await usersCollection.updateMany(filter, updateDoc);
        
        return {
            reactivatedUsers: result.modifiedCount,
            totalMatched: result.matchedCount
        };
        
    } catch (error) {
        console.error('Error reactivating users:', error);
        throw error;
    }
}

Updating with findOneAndUpdate() for retrieval and modification

The findOneAndUpdate() method combines finding and updating a document in a single atomic operation, returning either the original or updated document. This is particularly useful when you need to see the document’s state before or after the update.

async function updateUserAndReturn() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const usersCollection = db.collection('users');
        
        const filter = { email: '[email protected]' };
        const updateDoc = {
            $set: {
                lastLoginAt: new Date(),
                loginCount: { $inc: 1 }
            },
            $push: {
                loginHistory: {
                    timestamp: new Date(),
                    ipAddress: '192.168.1.1',
                    userAgent: 'Mozilla/5.0...'
                }
            }
        };
        
        const options = {
            returnDocument: 'after',  // Return updated document
            upsert: false,           // Don't create if not exists
            projection: {            // Only return specific fields
                password: 0,         // Exclude password
                internalNotes: 0     // Exclude internal fields
            }
        };
        
        const result = await usersCollection.findOneAndUpdate(filter, updateDoc, options);
        
        if (result.value) {
            console.log('User updated successfully:', result.value);
            return {
                success: true,
                user: result.value,
                message: 'Login recorded successfully'
            };
        } else {
            console.log('User not found');
            return {
                success: false,
                message: 'User not found'
            };
        }
        
    } catch (error) {
        console.error('Error updating user login:', error);
        throw error;
    }
}

// Shopping cart update example
async function updateShoppingCart(userId, productId, quantity) {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const cartsCollection = db.collection('shopping_carts');
        
        const filter = { 
            userId: new ObjectId(userId),
            'items.productId': new ObjectId(productId)
        };
        
        // First, try to update existing item quantity
        let updateDoc = {
            $set: {
                'items.$.quantity': quantity,
                'items.$.updatedAt': new Date(),
                updatedAt: new Date()
            }
        };
        
        let result = await cartsCollection.findOneAndUpdate(
            filter, 
            updateDoc, 
            { returnDocument: 'after' }
        );
        
        // If item doesn't exist in cart, add it
        if (!result.value) {
            const addItemFilter = { userId: new ObjectId(userId) };
            const addItemUpdate = {
                $push: {
                    items: {
                        productId: new ObjectId(productId),
                        quantity: quantity,
                        addedAt: new Date(),
                        updatedAt: new Date()
                    }
                },
                $set: {
                    updatedAt: new Date()
                }
            };
            
            result = await cartsCollection.findOneAndUpdate(
                addItemFilter,
                addItemUpdate,
                { 
                    returnDocument: 'after',
                    upsert: true  // Create cart if doesn't exist
                }
            );
        }
        
        return {
            success: true,
            cart: result.value,
            message: 'Cart updated successfully'
        };
        
    } catch (error) {
        console.error('Error updating shopping cart:', error);
        return {
            success: false,
            message: error.message
        };
    }
}

Working with update operators like $set, $inc, $push, and $unset

MongoDB provides powerful update operators that enable precise document modifications without replacing entire documents. Understanding these operators is crucial for efficient updates.

$set Operator - Setting Field Values:

async function demonstrateSetOperator() {
    const db = dbConnection.getDatabase();
    const usersCollection = db.collection('users');
    
    // Set multiple fields at once
    const updateDoc = {
        $set: {
            firstName: 'John',
            lastName: 'Doe',
            'profile.bio': 'Software developer',  // Nested field
            'settings.theme': 'dark',             // Nested field
            updatedAt: new Date()
        }
    };
    
    await usersCollection.updateOne(
        { email: '[email protected]' },
        updateDoc
    );
}

$inc Operator - Incrementing Numeric Values:

async function demonstrateIncOperator() {
    const db = dbConnection.getDatabase();
    const postsCollection = db.collection('posts');
    
    // Increment view count and like count
    const updateDoc = {
        $inc: {
            viewCount: 1,           // Increment by 1
            likeCount: 5,           // Increment by 5
            'stats.shares': 1       // Nested field increment
        },
        $set: {
            lastViewedAt: new Date()
        }
    };
    
    const result = await postsCollection.updateOne(
        { _id: new ObjectId('507f1f77bcf86cd799439011') },
        updateDoc
    );
    
    return result;
}

$push Operator - Adding Elements to Arrays:

async function demonstratePushOperator() {
    const db = dbConnection.getDatabase();
    const usersCollection = db.collection('users');
    
    // Add single item to array
    let updateDoc = {
        $push: {
            interests: 'photography'
        }
    };
    
    await usersCollection.updateOne(
        { email: '[email protected]' },
        updateDoc
    );
    
    // Add multiple items to array
    updateDoc = {
        $push: {
            interests: {
                $each: ['travel', 'cooking', 'reading']  // Add multiple items
            }
        }
    };
    
    await usersCollection.updateOne(
        { email: '[email protected]' },
        updateDoc
    );
    
    // Add with sorting and size limit
    updateDoc = {
        $push: {
            recentActivities: {
                $each: [
                    {
                        action: 'login',
                        timestamp: new Date(),
                        ip: '192.168.1.1'
                    }
                ],
                $sort: { timestamp: -1 },  // Sort by timestamp descending
                $slice: 10                 // Keep only latest 10 activities
            }
        }
    };
    
    await usersCollection.updateOne(
        { email: '[email protected]' },
        updateDoc
    );
}

$unset Operator - Removing Fields:

async function demonstrateUnsetOperator() {
    const db = dbConnection.getDatabase();
    const usersCollection = db.collection('users');
    
    // Remove specific fields
    const updateDoc = {
        $unset: {
            temporaryToken: "",        // Remove field
            'profile.deprecated': "",  // Remove nested field
            oldSettings: ""           // Remove entire object
        },
        $set: {
            updatedAt: new Date()
        }
    };
    
    const result = await usersCollection.updateMany(
        { status: 'active' },
        updateDoc
    );
    
    return result;
}

Complex Update with Multiple Operators:

async function complexUserProfileUpdate(userId, updateData) {
    try {
        const db = dbConnection.getDatabase();
        const usersCollection = db.collection('users');
        
        const updateDoc = {};
        
        // Build update document based on provided data
        if (updateData.basicInfo) {
            updateDoc.$set = {
                ...updateDoc.$set,
                firstName: updateData.basicInfo.firstName,
                lastName: updateData.basicInfo.lastName,
                'profile.bio': updateData.basicInfo.bio,
                updatedAt: new Date()
            };
        }
        
        if (updateData.incrementStats) {
            updateDoc.$inc = {
                'stats.profileViews': updateData.incrementStats.profileViews || 0,
                'stats.connections': updateData.incrementStats.connections || 0
            };
        }
        
        if (updateData.addSkills && updateData.addSkills.length > 0) {
            updateDoc.$push = {
                'profile.skills': {
                    $each: updateData.addSkills
                }
            };
        }
        
        if (updateData.removeFields && updateData.removeFields.length > 0) {
            updateDoc.$unset = {};
            updateData.removeFields.forEach(field => {
                updateDoc.$unset[field] = "";
            });
        }
        
        const result = await usersCollection.findOneAndUpdate(
            { _id: new ObjectId(userId) },
            updateDoc,
            { 
                returnDocument: 'after',
                projection: { password: 0 }  // Exclude sensitive data
            }
        );
        
        return {
            success: !!result.value,
            user: result.value,
            message: result.value ? 'Profile updated successfully' : 'User not found'
        };
        
    } catch (error) {
        console.error('Error in complex profile update:', error);
        return {
            success: false,
            message: error.message
        };
    }
}

// Usage example
const updateData = {
    basicInfo: {
        firstName: 'Jane',
        lastName: 'Smith',
        bio: 'Full-stack developer and tech enthusiast'
    },
    incrementStats: {
        profileViews: 1,
        connections: 2
    },
    addSkills: ['React', 'Node.js', 'MongoDB'],
    removeFields: ['temporaryData', 'oldPreferences']
};

// complexUserProfileUpdate('507f1f77bcf86cd799439011', updateData);

Practical Examples: Update Operations in Action

Updating a single user’s email address by ID

Email address updates are common in user management systems and require careful handling to maintain data integrity and user experience:

const { ObjectId } = require('mongodb');

class UserEmailManager {
    constructor(database) {
        this.db = database;
        this.usersCollection = this.db.collection('users');
    }
    
    async updateUserEmail(userId, newEmail, currentPassword) {
        try {
            // Input validation
            if (!ObjectId.isValid(userId)) {
                throw new Error('Invalid user ID format');
            }
            
            if (!this.isValidEmail(newEmail)) {
                throw new Error('Invalid email format');
            }
            
            // Check if email is already in use
            const existingUser = await this.usersCollection.findOne({ 
                email: newEmail.toLowerCase(),
                _id: { $ne: new ObjectId(userId) }
            });
            
            if (existingUser) {
                return {
                    success: false,
                    message: 'Email address is already in use by another account'
                };
            }
            
            // Find current user and verify password
            const currentUser = await this.usersCollection.findOne({ 
                _id: new ObjectId(userId) 
            });
            
            if (!currentUser) {
                return {
                    success: false,
                    message: 'User not found'
                };
            }
            
            // In a real application, you would verify the password here
            // const isPasswordValid = await bcrypt.compare(currentPassword, currentUser.password);
            // if (!isPasswordValid) {
            //     return { success: false, message: 'Invalid password' };
            // }
            
            // Create email change record for audit trail
            const emailChangeRecord = {
                oldEmail: currentUser.email,
                newEmail: newEmail.toLowerCase(),
                changedAt: new Date(),
                changedBy: userId,
                ipAddress: null, // You would get this from the request
                verified: false
            };
            
            // Update user with new email and change history
            const updateDoc = {
                $set: {
                    email: newEmail.toLowerCase(),
                    emailVerified: false,  // Require re-verification
                    updatedAt: new Date()
                },
                $push: {
                    emailHistory: emailChangeRecord
                },
                $inc: {
                    emailChangeCount: 1
                }
            };
            
            const updateOptions = {
                returnDocument: 'after',
                projection: {
                    password: 0,           // Exclude password from response
                    emailHistory: { $slice: -5 }  // Only return last 5 changes
                }
            };
            
            const result = await this.usersCollection.findOneAndUpdate(
                { _id: new ObjectId(userId) },
                updateDoc,
                updateOptions
            );
            
            if (result.value) {
                // In a real application, send verification email here
                await this.sendEmailVerification(result.value.email, userId);
                
                return {
                    success: true,
                    message: 'Email updated successfully. Please check your new email for verification.',
                    user: result.value,
                    requiresVerification: true
                };
            } else {
                return {
                    success: false,
                    message: 'Failed to update email address'
                };
            }
            
        } catch (error) {
            console.error('Error updating user email:', error);
            return {
                success: false,
                message: 'An error occurred while updating the email address'
            };
        }
    }
    
    async verifyEmailChange(userId, verificationToken) {
        try {
            const updateDoc = {
                $set: {
                    emailVerified: true,
                    emailVerifiedAt: new Date(),
                    updatedAt: new Date()
                },
                $unset: {
                    emailVerificationToken: "",
                    emailVerificationExpires: ""
                }
            };
            
            const result = await this.usersCollection.updateOne(
                { 
                    _id: new ObjectId(userId),
                    emailVerificationToken: verificationToken,
                    emailVerificationExpires: { $gt: new Date() }
                },
                updateDoc
            );
            
            return {
                success: result.modifiedCount === 1,
                message: result.modifiedCount === 1 
                    ? 'Email verified successfully' 
                    : 'Invalid or expired verification token'
            };
            
        } catch (error) {
            console.error('Error verifying email change:', error);
            return {
                success: false,
                message: 'Error verifying email change'
            };
        }
    }
    
    isValidEmail(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
    
    async sendEmailVerification(email, userId) {
        // Placeholder for email verification logic
        console.log(`Sending verification email to ${email} for user ${userId}`);
        
        // Generate verification token and save it
        const verificationToken = this.generateVerificationToken();
        const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
        
        await this.usersCollection.updateOne(
            { _id: new ObjectId(userId) },
            {
                $set: {
                    emailVerificationToken: verificationToken,
                    emailVerificationExpires: expiresAt
                }
            }
        );
        
        return verificationToken;
    }
    
    generateVerificationToken() {
        return require('crypto').randomBytes(32).toString('hex');
    }
}

// Usage example
async function demonstrateEmailUpdate() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const emailManager = new UserEmailManager(db);
        
        const userId = '507f1f77bcf86cd799439011';
        const newEmail = '[email protected]';
        const currentPassword = 'userPassword123';
        
        const result = await emailManager.updateUserEmail(userId, newEmail, currentPassword);
        
        console.log('Email update result:', result);
        
        if (result.success && result.requiresVerification) {
            console.log('User should check their email for verification link');
        }
        
    } catch (error) {
        console.error('Email update demonstration failed:', error);
    } finally {
        await dbConnection.disconnect();
    }
}

Bulk updating multiple product prices in a collection

E-commerce applications frequently need to update prices across multiple products, whether for promotions, seasonal adjustments, or market changes:

class ProductPriceManager {
    constructor(database) {
        this.db = database;
        this.productsCollection = this.db.collection('products');
        this.priceHistoryCollection = this.db.collection('price_history');
    }
    
    async bulkUpdatePrices(priceUpdates, updateReason = 'Manual adjustment') {
        try {
            // Validate input
            if (!Array.isArray(priceUpdates) || priceUpdates.length === 0) {
                throw new Error('Price updates array is required and cannot be empty');
            }
            
            const results = {
                successful: [],
                failed: [],
                summary: {
                    total: priceUpdates.length,
                    successful: 0,
                    failed: 0,
                    totalRevenue: 0
                }
            };
            
            // Process updates in batches to avoid memory issues
            const batchSize = 100;
            const batches = this.createBatches(priceUpdates, batchSize);
            
            for (const batch of batches) {
                await this.processPriceBatch(batch, updateReason, results);
            }
            
            // Generate summary report
            results.summary.successful = results.successful.length;
            results.summary.failed = results.failed.length;
            
            return results;
            
        } catch (error) {
            console.error('Error in bulk price update:', error);
            throw error;
        }
    }
    
    async processPriceBatch(batch, updateReason, results) {
        const session = this.db.client.startSession();
        
        try {
            await session.withTransaction(async () => {
                for (const update of batch) {
                    try {
                        const result = await this.updateSingleProductPrice(
                            update.productId,
                            update.newPrice,
                            update.salePrice,
                            updateReason,
                            session
                        );
                        
                        if (result.success) {
                            results.successful.push({
                                productId: update.productId,
                                oldPrice: result.oldPrice,
                                newPrice: update.newPrice,
                                priceChange: update.newPrice - result.oldPrice,
                                updatedAt: new Date()
                            });
                        } else {
                            results.failed.push({
                                productId: update.productId,
                                error: result.message
                            });
                        }
                        
                    } catch (error) {
                        results.failed.push({
                            productId: update.productId,
                            error: error.message
                        });
                    }
                }
            });
            
        } finally {
            await session.endSession();
        }
    }
    
    async updateSingleProductPrice(productId, newPrice, salePrice, reason, session) {
        try {
            // Validate price
            if (newPrice <= 0) {
                throw new Error('Price must be greater than 0');
            }
            
            // Get current product data
            const currentProduct = await this.productsCollection.findOne(
                { _id: new ObjectId(productId) },
                { session }
            );
            
            if (!currentProduct) {
                return {
                    success: false,
                    message: 'Product not found'
                };
            }
            
            const oldPrice = currentProduct.price;
            const priceChange = newPrice - oldPrice;
            const changePercentage = ((priceChange / oldPrice) * 100).toFixed(2);
            
            // Create price history record
            const priceHistoryRecord = {
                productId: new ObjectId(productId),
                oldPrice: oldPrice,
                newPrice: newPrice,
                salePrice: salePrice || null,
                priceChange: priceChange,
                changePercentage: parseFloat(changePercentage),
                reason: reason,
                changedAt: new Date(),
                changedBy: 'system' // In real app, this would be the user ID
            };
            
            // Insert price history
            await this.priceHistoryCollection.insertOne(priceHistoryRecord, { session });
            
            // Update product with new price
            const updateDoc = {
                $set: {
                    price: newPrice,
                    updatedAt: new Date(),
                    lastPriceChangeAt: new Date(),
                    lastPriceChangeReason: reason
                },
                $inc: {
                    priceChangeCount: 1
                }
            };
            
            // Add sale price if provided
            if (salePrice && salePrice < newPrice) {
                updateDoc.$set.salePrice = salePrice;
                updateDoc.$set.onSale = true;
                updateDoc.$set.saleStartDate = new Date();
            } else {
                updateDoc.$unset = {
                    salePrice: "",
                    onSale: "",
                    saleStartDate: "",
                    saleEndDate: ""
                };
            }
            
            const updateResult = await this.productsCollection.updateOne(
                { _id: new ObjectId(productId) },
                updateDoc,
                { session }
            );
            
            if (updateResult.modifiedCount === 1) {
                return {
                    success: true,
                    oldPrice: oldPrice,
                    newPrice: newPrice,
                    priceChange: priceChange,
                    changePercentage: changePercentage
                };
            } else {
                return {
                    success: false,
                    message: 'Failed to update product price'
                };
            }
            
        } catch (error) {
            console.error(`Error updating price for product ${productId}:`, error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    async applyPercentageDiscount(filter, discountPercentage, reason = 'Promotional discount') {
        try {
            if (discountPercentage <= 0 || discountPercentage >= 100) {
                throw new Error('Discount percentage must be between 0 and 100');
            }
            
            const discountMultiplier = (100 - discountPercentage) / 100;
            
            // Find products that match the filter
            const products = await this.productsCollection.find(filter).toArray();
            
            console.log(`Applying ${discountPercentage}% discount to ${products.length} products`);
            
            // Create price updates array
            const priceUpdates = products.map(product => ({
                productId: product._id.toString(),
                newPrice: Math.round(product.price * discountMultiplier * 100) / 100, // Round to 2 decimal places
                salePrice: null
            }));
            
            return await this.bulkUpdatePrices(priceUpdates, reason);
            
        } catch (error) {
            console.error('Error applying percentage discount:', error);
            throw error;
        }
    }
    
    async categoryPriceUpdate(category, priceAdjustment, isPercentage = false) {
        try {
            const filter = { category: category, status: 'active' };
            
            if (isPercentage) {
                return await this.applyPercentageDiscount(filter, Math.abs(priceAdjustment), 
                    `Category price adjustment: ${priceAdjustment}%`);
            } else {
                // Fixed amount adjustment
                const products = await this.productsCollection.find(filter).toArray();
                
                const priceUpdates = products.map(product => ({
                    productId: product._id.toString(),
                    newPrice: Math.max(0.01, product.price + priceAdjustment), // Ensure price stays positive
                    salePrice: null
                }));
                
                return await this.bulkUpdatePrices(priceUpdates, 
                    `Category price adjustment: $${priceAdjustment}`);
            }
            
        } catch (error) {
            console.error('Error updating category prices:', error);
            throw error;
        }
    }
    
    createBatches(array, batchSize) {
        const batches = [];
        for (let i = 0; i < array.length; i += batchSize) {
            batches.push(array.slice(i, i + batchSize));
        }
        return batches;
    }
}

// Usage examples
async function demonstrateBulkPriceUpdates() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const priceManager = new ProductPriceManager(db);
        
        // Example 1: Manual price updates for specific products
        const manualUpdates = [
            { productId: '507f1f77bcf86cd799439011', newPrice: 29.99, salePrice: 24.99 },
            { productId: '507f1f77bcf86cd799439012', newPrice: 49.99 },
            { productId: '507f1f77bcf86cd799439013', newPrice: 19.99 }
        ];
        
        console.log('Applying manual price updates...');
        const manualResult = await priceManager.bulkUpdatePrices(manualUpdates, 'Manual price adjustment');
        console.log('Manual update results:', manualResult.summary);
        
        // Example 2: Apply 15% discount to all electronics
        console.log('Applying 15% discount to electronics...');
        const discountResult = await priceManager.applyPercentageDiscount(
            { category: 'Electronics' },
            15,
            'Black Friday Sale'
        );
        console.log('Discount results:', discountResult.summary);
        
        // Example 3: Increase all clothing prices by $5
        console.log('Increasing clothing prices by $5...');
        const increaseResult = await priceManager.categoryPriceUpdate('Clothing', 5, false);
        console.log('Price increase results:', increaseResult.summary);
        
    } catch (error) {
        console.error('Price update demonstration failed:', error);
    } finally {
        await dbConnection.disconnect();
    }
}

Using dynamic fields and condition-based updates

Dynamic updates based on conditions are essential for building flexible applications that can handle complex business logic:

class DynamicUpdateManager {
    constructor(database) {
        this.db = database;
    }
    
    async conditionalUserUpdate(userId, conditions, updates) {
        try {
            const usersCollection = this.db.collection('users');
            
            // Build dynamic filter based on conditions
            const filter = this.buildDynamicFilter(userId, conditions);
            
            // Build dynamic update document
            const updateDoc = this.buildDynamicUpdate(updates);
            
            // Perform conditional update
            const result = await usersCollection.findOneAndUpdate(
                filter,
                updateDoc,
                { 
                    returnDocument: 'after',
                    projection: { password: 0, sensitiveData: 0 }
                }
            );
            
            return {
                success: !!result.value,
                user: result.value,
                message: result.value ? 'User updated successfully' : 'Update conditions not met'
            };
            
        } catch (error) {
            console.error('Error in conditional user update:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    buildDynamicFilter(userId, conditions) {
        const filter = { _id: new ObjectId(userId) };
        
        // Add conditional filters
        if (conditions.minLastLogin) {
            filter.lastLoginAt = { $gte: new Date(conditions.minLastLogin) };
        }
        
        if (conditions.status) {
            filter.status = { $in: Array.isArray(conditions.status) ? conditions.status : [conditions.status] };
        }
        
        if (conditions.emailVerified !== undefined) {
            filter.emailVerified = conditions.emailVerified;
        }
        
        if (conditions.accountAge) {
            const ageDate = new Date();
            ageDate.setDate(ageDate.getDate() - conditions.accountAge);
            filter.createdAt = { $lte: ageDate };
        }
        
        if (conditions.customFields) {
            Object.assign(filter, conditions.customFields);
        }
        
        return filter;
    }
    
    buildDynamicUpdate(updates) {
        const updateDoc = {};
        
        // Handle $set operations
        if (updates.set && Object.keys(updates.set).length > 0) {
            updateDoc.$set = {
                ...updates.set,
                updatedAt: new Date()
            };
        }
        
        // Handle $inc operations
        if (updates.increment && Object.keys(updates.increment).length > 0) {
            updateDoc.$inc = updates.increment;
        }
        
        // Handle $push operations
        if (updates.push && Object.keys(updates.push).length > 0) {
            updateDoc.$push = {};
            for (const [field, value] of Object.entries(updates.push)) {
                if (Array.isArray(value)) {
                    updateDoc.$push[field] = { $each: value };
                } else {
                    updateDoc.$push[field] = value;
                }
            }
        }
        
        // Handle $pull operations
        if (updates.pull && Object.keys(updates.pull).length > 0) {
            updateDoc.$pull = updates.pull;
        }
        
        // Handle $unset operations
        if (updates.unset && Array.isArray(updates.unset)) {
            updateDoc.$unset = {};
            updates.unset.forEach(field => {
                updateDoc.$unset[field] = "";
            });
        }
        
        // Add timestamp if not already set
        if (!updateDoc.$set) {
            updateDoc.$set = { updatedAt: new Date() };
        }
        
        return updateDoc;
    }
    
    async smartInventoryUpdate(productId, quantityChange, conditions = {}) {
        try {
            const productsCollection = this.db.collection('products');
            
            // Build filter with inventory conditions
            const filter = {
                _id: new ObjectId(productId),
                status: 'active'
            };
            
            // Add minimum stock condition
            if (quantityChange < 0) {
                filter['inventory.quantity'] = { $gte: Math.abs(quantityChange) };
            }
            
            // Add custom conditions
            if (conditions.minStock) {
                filter['inventory.quantity'] = { 
                    ...filter['inventory.quantity'],
                    $gte: conditions.minStock
                };
            }
            
            // Build update document
            const updateDoc = {
                $inc: {
                    'inventory.quantity': quantityChange
                },
                $set: {
                    'inventory.lastUpdated': new Date(),
                    updatedAt: new Date()
                }
            };
            
            // Add low stock warning if needed
            const product = await productsCollection.findOne({ _id: new ObjectId(productId) });
            if (product) {
                const newQuantity = product.inventory.quantity + quantityChange;
                const threshold = product.inventory.lowStockThreshold || 10;
                
                if (newQuantity <= threshold) {
                    updateDoc.$set['inventory.lowStockAlert'] = true;
                    updateDoc.$set['inventory.alertTriggeredAt'] = new Date();
                } else {
                    updateDoc.$unset = {
                        'inventory.lowStockAlert': "",
                        'inventory.alertTriggeredAt': ""
                    };
                }
            }
            
            const result = await productsCollection.findOneAndUpdate(
                filter,
                updateDoc,
                { returnDocument: 'after' }
            );
            
            return {
                success: !!result.value,
                product: result.value,
                message: result.value ? 'Inventory updated successfully' : 'Insufficient stock or conditions not met'
            };
            
        } catch (error) {
            console.error('Error in smart inventory update:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    async batchConditionalUpdate(collection, updates) {
        try {
            const targetCollection = this.db.collection(collection);
            const results = {
                successful: 0,
                failed: 0,
                details: []
            };
            
            for (const update of updates) {
                try {
                    const filter = update.filter || {};
                    const updateDoc = this.buildDynamicUpdate(update.updates);
                    
                    const result = await targetCollection.updateMany(filter, updateDoc);
                    
                    results.successful += result.modifiedCount;
                    results.details.push({
                        filter: filter,
                        matched: result.matchedCount,
                        modified: result.modifiedCount,
                        success: true
                    });
                    
                } catch (error) {
                    results.failed++;
                    results.details.push({
                        filter: update.filter,
                        error: error.message,
                        success: false
                    });
                }
            }
            
            return results;
            
        } catch (error) {
            console.error('Error in batch conditional update:', error);
            throw error;
        }
    }
}

// Usage examples
async function demonstrateDynamicUpdates() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const updateManager = new DynamicUpdateManager(db);
        
        // Example 1: Conditional user update
        const conditions = {
            status: ['active', 'pending'],
            emailVerified: true,
            minLastLogin: '2024-01-01'
        };
        
        const updates = {
            set: {
                'profile.membershipLevel': 'premium',
                'settings.notifications': true
            },
            increment: {
                'stats.logins': 1,
                'points.balance': 100
            },
            push: {
                'achievements': 'loyal_customer',
                'notifications': {
                    type: 'upgrade',
                    message: 'Congratulations on your premium upgrade!',
                    createdAt: new Date()
                }
            }
        };
        
        const result = await updateManager.conditionalUserUpdate(
            '507f1f77bcf86cd799439011',
            conditions,
            updates
        );
        
        console.log('Conditional update result:', result);
        
        // Example 2: Smart inventory update
        const inventoryResult = await updateManager.smartInventoryUpdate(
            '507f1f77bcf86cd799439021',
            -5,  // Decrease by 5
            { minStock: 10 }
        );
        
        console.log('Inventory update result:', inventoryResult);
        
    } catch (error) {
        console.error('Dynamic update demonstration failed:', error);
    } finally {
        await dbConnection.disconnect();
    }
}

Handling Edge Cases and Common Pitfalls

Dealing with non-existent records during update

One of the most common challenges when updating MongoDB documents is handling cases where the target document doesn’t exist. Proper error handling ensures your application gracefully manages these scenarios:

class RobustUpdateManager {
    constructor(database) {
        this.db = database;
    }
    
    async safeUpdateWithValidation(collection, filter, updateDoc, options = {}) {
        try {
            const targetCollection = this.db.collection(collection);
            
            // First, check if document exists
            const existingDoc = await targetCollection.findOne(filter);
            
            if (!existingDoc) {
                return {
                    success: false,
                    code: 'DOCUMENT_NOT_FOUND',
                    message: 'Document not found for the specified criteria',
                    shouldRetry: false
                };
            }
            
            // Perform the update
            const result = await targetCollection.findOneAndUpdate(
                filter,
                updateDoc,
                {
                    returnDocument: 'after',
                    ...options
                }
            );
            
            return {
                success: true,
                document: result.value,
                message: 'Document updated successfully'
            };
            
        } catch (error) {
            return this.handleUpdateError(error);
        }
    }
    
    async updateWithUpsert(collection, filter, updateDoc, createDoc = null) {
        try {
            const targetCollection = this.db.collection(collection);
            
            // Attempt update first
            const updateResult = await targetCollection.findOneAndUpdate(
                filter,
                updateDoc,
                { returnDocument: 'after' }
            );
            
            if (updateResult.value) {
                return {
                    success: true,
                    document: updateResult.value,
                    action: 'updated',
                    message: 'Document updated successfully'
                };
            }
            
            // Document doesn't exist, create it if createDoc is provided
            if (createDoc) {
                const newDoc = {
                    ...createDoc,
                    ...this.extractSetValues(updateDoc),
                    createdAt: new Date(),
                    updatedAt: new Date()
                };
                
                const insertResult = await targetCollection.insertOne(newDoc);
                
                return {
                    success: true,
                    document: { _id: insertResult.insertedId, ...newDoc },
                    action: 'created',
                    message: 'Document created successfully'
                };
            } else {
                // Use MongoDB's upsert option
                const upsertResult = await targetCollection.findOneAndUpdate(
                    filter,
                    {
                        ...updateDoc,
                        $setOnInsert: {
                            createdAt: new Date(),
                            ...filter  // Include filter fields in new document
                        }
                    },
                    {
                        upsert: true,
                        returnDocument: 'after'
                    }
                );
                
                return {
                    success: true,
                    document: upsertResult.value,
                    action: upsertResult.lastErrorObject.updatedExisting ? 'updated' : 'created',
                    message: upsertResult.lastErrorObject.updatedExisting 
                        ? 'Document updated successfully' 
                        : 'Document created successfully'
                };
            }
            
        } catch (error) {
            return this.handleUpdateError(error);
        }
    }
    
    async retryableUpdate(collection, filter, updateDoc, maxRetries = 3) {
        let lastError;
        
        for (let attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                const result = await this.safeUpdateWithValidation(collection, filter, updateDoc);
                
                if (result.success || !result.shouldRetry) {
                    return result;
                }
                
                // Wait before retrying
                if (attempt < maxRetries) {
                    const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
                    await new Promise(resolve => setTimeout(resolve, delay));
                }
                
            } catch (error) {
                lastError = error;
                console.warn(`Update attempt ${attempt} failed:`, error.message);
            }
        }
        
        return {
            success: false,
            code: 'MAX_RETRIES_EXCEEDED',
            message: `Failed after ${maxRetries} attempts`,
            lastError: lastError
        };
    }
    
    extractSetValues(updateDoc) {
        // Extract values from $set operator for upsert operations
        return updateDoc.$set || {};
    }
    
    handleUpdateError(error) {
        console.error('Update operation failed:', error);
        
        if (error.code === 11000) {
            return {
                success: false,
                code: 'DUPLICATE_KEY',
                message: 'Document with this key already exists',
                shouldRetry: false
            };
        }
        
        if (error.name === 'MongoTimeoutError') {
            return {
                success: false,
                code: 'TIMEOUT',
                message: 'Update operation timed out',
                shouldRetry: true
            };
        }
        
        if (error.name === 'MongoNetworkError') {
            return {
                success: false,
                code: 'NETWORK_ERROR',
                message: 'Network connection failed',
                shouldRetry: true
            };
        }
        
        return {
            success: false,
            code: 'UNKNOWN_ERROR',
            message: error.message,
            shouldRetry: false
        };
    }
}

// Usage examples
async function demonstrateEdgeCaseHandling() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const updateManager = new RobustUpdateManager(db);
        
        // Example 1: Safe update with validation
        const safeResult = await updateManager.safeUpdateWithValidation(
            'users',
            { email: '[email protected]' },
            { $set: { lastLoginAt: new Date() } }
        );
        
        console.log('Safe update result:', safeResult);
        
        // Example 2: Update with upsert fallback
        const upsertResult = await updateManager.updateWithUpsert(
            'user_preferences',
            { userId: '507f1f77bcf86cd799439011' },
            { 
                $set: { 
                    theme: 'dark',
                    language: 'en',
                    updatedAt: new Date()
                }
            },
            { 
                userId: '507f1f77bcf86cd799439011',
                defaultSettings: true
            }
        );
        
        console.log('Upsert result:', upsertResult);
        
        // Example 3: Retryable update for unstable connections
        const retryResult = await updateManager.retryableUpdate(
            'products',
            { sku: 'PROD-001' },
            { 
                $inc: { viewCount: 1 },
                $set: { lastViewedAt: new Date() }
            },
            3
        );
        
        console.log('Retry result:', retryResult);
        
    } catch (error) {
        console.error('Edge case demonstration failed:', error);
    }
}

Preventing overwrite of unintended fields

Accidental field overwrites can cause data loss and application errors. Implementing proper safeguards protects your data integrity:

class SafeUpdateManager {
    constructor(database) {
        this.db = database;
        this.protectedFields = ['_id', 'createdAt', 'systemGenerated'];
        this.sensitiveFields = ['password', 'apiKeys', 'internalNotes'];
    }
    
    async protectedUpdate(collection, filter, updateDoc, options = {}) {
        try {
            // Validate update document to prevent dangerous operations
            const validationResult = this.validateUpdateDocument(updateDoc, options);
            if (!validationResult.isValid) {
                return {
                    success: false,
                    code: 'VALIDATION_FAILED',
                    message: validationResult.message,
                    errors: validationResult.errors
                };
            }
            
            const targetCollection = this.db.collection(collection);
            
            // Get current document to check for protected fields
            const currentDoc = await targetCollection.findOne(filter);
            if (!currentDoc) {
                return {
                    success: false,
                    code: 'DOCUMENT_NOT_FOUND',
                    message: 'Document not found'
                };
            }
            
            // Create safe update document
            const safeUpdateDoc = this.createSafeUpdateDocument(updateDoc, currentDoc, options);
            
            const result = await targetCollection.findOneAndUpdate(
                filter,
                safeUpdateDoc,
                {
                    returnDocument: 'after',
                    projection: this.createSafeProjection(options.includeSensitive)
                }
            );
            
            return {
                success: true,
                document: result.value,
                message: 'Document updated safely'
            };
            
        } catch (error) {
            console.error('Protected update failed:', error);
            return {
                success: false,
                code: 'UPDATE_ERROR',
                message: error.message
            };
        }
    }
    
    validateUpdateDocument(updateDoc, options = {}) {
        const errors = [];
        
        // Check for protected field modifications
        if (updateDoc.$set) {
            for (const field of Object.keys(updateDoc.$set)) {
                if (this.protectedFields.includes(field)) {
                    errors.push(`Field '${field}' is protected and cannot be modified`);
                }
                
                if (!options.allowSensitive && this.sensitiveFields.includes(field)) {
                    errors.push(`Field '${field}' is sensitive and requires special permissions`);
                }
            }
        }
        
        // Check for dangerous unset operations
        if (updateDoc.$unset) {
            for (const field of Object.keys(updateDoc.$unset)) {
                if (this.protectedFields.includes(field)) {
                    errors.push(`Protected field '${field}' cannot be removed`);
                }
            }
        }
        
        // Validate operators
        const allowedOperators = ['$set', '$unset', '$inc', '$push', '$pull', '$addToSet'];
        const usedOperators = Object.keys(updateDoc);
        const invalidOperators = usedOperators.filter(op => !allowedOperators.includes(op));
        
        if (invalidOperators.length > 0) {
            errors.push(`Invalid operators: ${invalidOperators.join(', ')}`);
        }
        
        // Check for replace operations (documents without operators)
        if (usedOperators.length > 0 && !usedOperators.some(key => key.startsWith('$'))) {
            errors.push('Document replacement operations are not allowed - use update operators instead');
        }
        
        return {
            isValid: errors.length === 0,
            errors: errors,
            message: errors.length > 0 ? errors.join('; ') : null
        };
    }
    
    createSafeUpdateDocument(updateDoc, currentDoc, options = {}) {
        const safeUpdate = JSON.parse(JSON.stringify(updateDoc)); // Deep clone
        
        // Remove protected fields from $set
        if (safeUpdate.$set) {
            this.protectedFields.forEach(field => {
                delete safeUpdate.$set[field];
            });
            
            // Add updatedAt timestamp
            safeUpdate.$set.updatedAt = new Date();
            
            // Preserve existing sensitive fields if not explicitly allowed
            if (!options.allowSensitive) {
                this.sensitiveFields.forEach(field => {
                    delete safeUpdate.$set[field];
                });
            }
        }
        
        // Remove protected fields from $unset
        if (safeUpdate.$unset) {
            this.protectedFields.forEach(field => {
                delete safeUpdate.$unset[field];
            });
        }
        
        // Validate increment operations
        if (safeUpdate.$inc) {
            Object.keys(safeUpdate.$inc).forEach(field => {
                if (currentDoc[field] !== undefined && typeof currentDoc[field] !== 'number') {
                    delete safeUpdate.$inc[field];
                    console.warn(`Skipped increment on non-numeric field: ${field}`);
                }
            });
        }
        
        return safeUpdate;
    }
    
    createSafeProjection(includeSensitive = false) {
        const projection = {};
        
        if (!includeSensitive) {
            this.sensitiveFields.forEach(field => {
                projection[field] = 0;
            });
        }
        
        return projection;
    }
    
    async selectiveFieldUpdate(collection, filter, fieldUpdates, options = {}) {
        try {
            // Build update document from selective field updates
            const updateDoc = { $set: {} };
            
            // Whitelist approach - only allow specified fields
            const allowedFields = options.allowedFields || [];
            
            for (const [field, value] of Object.entries(fieldUpdates)) {
                if (allowedFields.length === 0 || allowedFields.includes(field)) {
                    if (!this.protectedFields.includes(field)) {
                        updateDoc.$set[field] = value;
                    }
                }
            }
            
            if (Object.keys(updateDoc.$set).length === 0) {
                return {
                    success: false,
                    code: 'NO_VALID_FIELDS',
                    message: 'No valid fields to update'
                };
            }
            
            return await this.protectedUpdate(collection, filter, updateDoc, options);
            
        } catch (error) {
            console.error('Selective field update failed:', error);
            return {
                success: false,
                code: 'UPDATE_ERROR',
                message: error.message
            };
        }
    }
    
    async atomicFieldUpdate(collection, filter, fieldName, updateFunction) {
        try {
            const targetCollection = this.db.collection(collection);
            const session = this.db.client.startSession();
            
            let result;
            await session.withTransaction(async () => {
                // Get current document
                const currentDoc = await targetCollection.findOne(filter, { session });
                
                if (!currentDoc) {
                    throw new Error('Document not found');
                }
                
                // Apply update function to field value
                const currentValue = this.getNestedValue(currentDoc, fieldName);
                const newValue = updateFunction(currentValue);
                
                // Create update document
                const updateDoc = {
                    $set: {
                        [fieldName]: newValue,
                        updatedAt: new Date()
                    }
                };
                
                // Perform atomic update
                result = await targetCollection.findOneAndUpdate(
                    filter,
                    updateDoc,
                    { 
                        session,
                        returnDocument: 'after'
                    }
                );
            });
            
            await session.endSession();
            
            return {
                success: true,
                document: result.value,
                message: 'Atomic field update completed'
            };
            
        } catch (error) {
            console.error('Atomic field update failed:', error);
            return {
                success: false,
                code: 'ATOMIC_UPDATE_ERROR',
                message: error.message
            };
        }
    }
    
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => current && current[key], obj);
    }
}

// Usage examples
async function demonstrateFieldProtection() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const safeManager = new SafeUpdateManager(db);
        
        // Example 1: Protected update that prevents overwriting sensitive fields
        const protectedResult = await safeManager.protectedUpdate(
            'users',
            { email: '[email protected]' },
            {
                $set: {
                    firstName: 'John',
                    lastName: 'Doe',
                    createdAt: new Date(), // This will be filtered out
                    password: 'newpass123' // This will be filtered out
                }
            }
        );
        
        console.log('Protected update result:', protectedResult);
        
        // Example 2: Selective field update with whitelist
        const selectiveResult = await safeManager.selectiveFieldUpdate(
            'users',
            { _id: new ObjectId('507f1f77bcf86cd799439011') },
            {
                firstName: 'Jane',
                lastName: 'Smith',
                email: '[email protected]',
                password: 'shouldnotupdate' // Will be ignored
            },
            {
                allowedFields: ['firstName', 'lastName', 'email']
            }
        );
        
        console.log('Selective update result:', selectiveResult);
        
        // Example 3: Atomic field update with custom logic
        const atomicResult = await safeManager.atomicFieldUpdate(
            'products',
            { sku: 'PROD-001' },
            'inventory.quantity',
            (currentQuantity) => Math.max(0, currentQuantity - 1)
        );
        
        console.log('Atomic update result:', atomicResult);
        
    } catch (error) {
        console.error('Field protection demonstration failed:', error);
    }
}

Optimizing performance with indexes and projections

Proper indexing and selective data retrieval are crucial for maintaining update performance as your database grows:

class PerformanceOptimizedUpdater {
    constructor(database) {
        this.db = database;
    }
    
    async ensureIndexes() {
        try {
            const collections = {
                users: [
                    { email: 1 },                    // Unique index for email lookups
                    { status: 1, lastLoginAt: -1 }, // Compound index for user activity queries
                    { 'profile.location.coordinates': '2dsphere' }, // Geospatial index
                    { createdAt: -1 },               // Time-based queries
                    { 'tags': 1 }                    // Multikey index for array fields
                ],
                products: [
                    { sku: 1 },                      // Unique index for SKU
                    { category: 1, price: 1 },      // Compound index for category/price queries
                    { 'inventory.quantity': 1 },     // Inventory tracking
                    { status: 1, featured: 1 },     // Product filtering
                    { name: 'text', description: 'text' } // Text search index
                ],
                orders: [
                    { userId: 1, createdAt: -1 },   // User order history
                    { status: 1 },                   // Order status filtering
                    { 'items.productId': 1 },        // Product in orders
                    { updatedAt: -1 }               // Recent updates
                ]
            };
            
            for (const [collectionName, indexes] of Object.entries(collections)) {
                const collection = this.db.collection(collectionName);
                
                for (const indexSpec of indexes) {
                    try {
                        await collection.createIndex(indexSpec);
                        console.log(`Created index on ${collectionName}:`, indexSpec);
                    } catch (error) {
                        if (error.code !== 85) { // Index already exists
                            console.error(`Failed to create index on ${collectionName}:`, error);
                        }
                    }
                }
            }
            
            console.log('Index creation completed');
            
        } catch (error) {
            console.error('Error ensuring indexes:', error);
        }
    }
    
    async optimizedBulkUpdate(collection, updates, batchSize = 1000) {
        try {
            const targetCollection = this.db.collection(collection);
            const results = {
                processed: 0,
                modified: 0,
                errors: []
            };
            
            // Process updates in batches to avoid memory issues
            for (let i = 0; i < updates.length; i += batchSize) {
                const batch = updates.slice(i, i + batchSize);
                
                try {
                    // Use bulkWrite for optimal performance
                    const bulkOps = batch.map(update => ({
                        updateOne: {
                            filter: update.filter,
                            update: update.update,
                            upsert: update.upsert || false
                        }
                    }));
                    
                    const batchResult = await targetCollection.bulkWrite(bulkOps, {
                        ordered: false  // Continue processing even if some operations fail
                    });
                    
                    results.processed += batch.length;
                    results.modified += batchResult.modifiedCount;
                    
                    console.log(`Processed batch ${Math.floor(i / batchSize) + 1}, modified ${batchResult.modifiedCount} documents`);
                    
                } catch (error) {
                    console.error(`Batch ${Math.floor(i / batchSize) + 1} failed:`, error);
                    results.errors.push({
                        batch: Math.floor(i / batchSize) + 1,
                        error: error.message
                    });
                }
            }
            
            return results;
            
        } catch (error) {
            console.error('Optimized bulk update failed:', error);
            throw error;
        }
    }
    
    async efficientFieldUpdate(collection, filter, updateDoc, options = {}) {
        try {
            const targetCollection = this.db.collection(collection);
            
            // Use projection to only return necessary fields
            const projection = options.projection || { 
                _id: 1, 
                updatedAt: 1,
                ...(options.returnFields || {})
            };
            
            // Add hint for index usage if specified
            const updateOptions = {
                returnDocument: 'after',
                projection: projection
            };
            
            if (options.hint) {
                updateOptions.hint = options.hint;
            }
            
            // Use explain to check query performance in development
            if (process.env.NODE_ENV === 'development' && options.explain) {
                const explanation = await targetCollection.find(filter).explain('executionStats');
                console.log('Query explanation:', {
                    executionTimeMillis: explanation.executionStats.executionTimeMillis,
                    totalDocsExamined: explanation.executionStats.totalDocsExamined,
                    totalDocsReturned: explanation.executionStats.totalDocsReturned,
                    indexUsed: explanation.executionStats.executionStages?.indexName
                });
            }
            
            const result = await targetCollection.findOneAndUpdate(
                filter,
                updateDoc,
                updateOptions
            );
            
            return {
                success: !!result.value,
                document: result.value,
                message: result.value ? 'Update completed efficiently' : 'Document not found'
            };
            
        } catch (error) {
            console.error('Efficient field update failed:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    async streamedUpdate(collection, filter, updateFunction, options = {}) {
        try {
            const targetCollection = this.db.collection(collection);
            const batchSize = options.batchSize || 100;
            let processed = 0;
            let modified = 0;
            
            // Use cursor for memory-efficient processing of large datasets
            const cursor = targetCollection.find(filter).batchSize(batchSize);
            
            while (await cursor.hasNext()) {
                const batch = [];
                
                // Collect batch of documents
                for (let i = 0; i < batchSize && await cursor.hasNext(); i++) {
                    batch.push(await cursor.next());
                }
                
                // Process batch
                const bulkOps = [];
                
                for (const doc of batch) {
                    try {
                        const updateDoc = await updateFunction(doc);
                        
                        if (updateDoc) {
                            bulkOps.push({
                                updateOne: {
                                    filter: { _id: doc._id },
                                    update: updateDoc
                                }
                            });
                        }
                    } catch (error) {
                        console.error(`Error processing document ${doc._id}:`, error);
                    }
                }
                
                // Execute batch updates
                if (bulkOps.length > 0) {
                    try {
                        const result = await targetCollection.bulkWrite(bulkOps, { ordered: false });
                        modified += result.modifiedCount;
                    } catch (error) {
                        console.error('Batch update failed:', error);
                    }
                }
                
                processed += batch.length;
                
                if (options.progressCallback) {
                    options.progressCallback(processed, modified);
                }
            }
            
            return {
                processed: processed,
                modified: modified,
                message: `Streamed update completed: ${modified} documents modified out of ${processed} processed`
            };
            
        } catch (error) {
            console.error('Streamed update failed:', error);
            throw error;
        }
    }
    
    async analyzeUpdatePerformance(collection, filter, updateDoc) {
        try {
            const targetCollection = this.db.collection(collection);
            
            // Get query explanation
            const explanation = await targetCollection.find(filter).explain('executionStats');
            
            // Perform the update and measure time
            const startTime = Date.now();
            const result = await targetCollection.updateMany(filter, updateDoc);
            const duration = Date.now() - startTime;
            
            return {
                performance: {
                    duration: duration,
                    docsExamined: explanation.executionStats.totalDocsExamined,
                    docsReturned: explanation.executionStats.totalDocsReturned,
                    indexUsed: explanation.executionStats.executionStages?.indexName || 'No index',
                    efficiency: explanation.executionStats.totalDocsReturned / explanation.executionStats.totalDocsExamined
                },
                updateResult: {
                    matched: result.matchedCount,
                    modified: result.modifiedCount
                },
                recommendations: this.generatePerformanceRecommendations(explanation)
            };
            
        } catch (error) {
            console.error('Performance analysis failed:', error);
            throw error;
        }
    }
    
    generatePerformanceRecommendations(explanation) {
        const recommendations = [];
        const stats = explanation.executionStats;
        
        if (stats.totalDocsExamined > stats.totalDocsReturned * 10) {
            recommendations.push('Consider adding an index to reduce document examination');
        }
        
        if (stats.executionTimeMillis > 100) {
            recommendations.push('Query execution time is high, check indexing strategy');
        }
        
        if (!stats.executionStages?.indexName) {
            recommendations.push('No index used - consider creating appropriate indexes');
        }
        
        return recommendations;
    }
}

// Usage examples
async function demonstratePerformanceOptimization() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const optimizer = new PerformanceOptimizedUpdater(db);
        
        // Ensure indexes are in place
        await optimizer.ensureIndexes();
        
        // Example 1: Optimized bulk update
        const bulkUpdates = [
            {
                filter: { category: 'Electronics' },
                update: { $mul: { price: 0.9 } }
            },
            {
                filter: { category: 'Clothing' },
                update: { $inc: { popularity: 1 } }
            }
        ];
        
        const bulkResult = await optimizer.optimizedBulkUpdate('products', bulkUpdates, 500);
        console.log('Bulk update results:', bulkResult);
        
        // Example 2: Efficient field update with projection
        const efficientResult = await optimizer.efficientFieldUpdate(
            'users',
            { status: 'active' },
            { $set: { lastNotificationAt: new Date() } },
            {
                projection: { _id: 1, email: 1, lastNotificationAt: 1 },
                hint: { status: 1 },
                explain: true
            }
        );
        
        console.log('Efficient update result:', efficientResult);
        
        // Example 3: Performance analysis
        const analysisResult = await optimizer.analyzeUpdatePerformance(
            'products',
            { price: { $gt: 100 } },
            { $set: { premium: true } }
        );
        
        console.log('Performance analysis:', analysisResult);
        
    } catch (error) {
        console.error('Performance optimization demonstration failed:', error);
    }
}

Using Mongoose for Schema-Based Updates (Optional Section)

Defining Mongoose schemas and models

Mongoose provides a powerful abstraction layer over MongoDB that includes schema validation, middleware hooks, and more intuitive update methods:

const mongoose = require('mongoose');

// User schema with validation and middleware
const userSchema = new mongoose.Schema({
    email: {
        type: String,
        required: [true, 'Email is required'],
        unique: true,
        lowercase: true,
        trim: true,
        validate: {
            validator: function(email) {
                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
            },
            message: 'Please provide a valid email address'
        }
    },
    firstName: {
        type: String,
        required: [true, 'First name is required'],
        trim: true,
        minlength: [2, 'First name must be at least 2 characters'],
        maxlength: [50, 'First name cannot exceed 50 characters']
    },
    lastName: {
        type: String,
        required: [true, 'Last name is required'],
        trim: true,
        minlength: [2, 'Last name must be at least 2 characters'],
        maxlength: [50, 'Last name cannot exceed 50 characters']
    },
    profile: {
        bio: {
            type: String,
            maxlength: [500, 'Bio cannot exceed 500 characters']
        },
        avatar: String,
        dateOfBirth: Date,
        location: {
            city: String,
            country: String,
            coordinates: {
                type: [Number], // [longitude, latitude]
                index: '2dsphere'
            }
        }
    },
    preferences: {
        theme: {
            type: String,
            enum: ['light', 'dark', 'auto'],
            default: 'light'
        },
        language: {
            type: String,
            default: 'en'
        },
        notifications: {
            email: { type: Boolean, default: true },
            push: { type: Boolean, default: true },
            sms: { type: Boolean, default: false }
        }
    },
    status: {
        type: String,
        enum: ['active', 'inactive', 'suspended', 'pending'],
        default: 'pending'
    },
    emailVerified: {
        type: Boolean,
        default: false
    },
    lastLoginAt: Date,
    loginCount: {
        type: Number,
        default: 0
    }
}, {
    timestamps: true, // Automatically adds createdAt and updatedAt
    versionKey: false
});

// Pre-save middleware
userSchema.pre('save', function(next) {
    if (this.isModified('email')) {
        this.emailVerified = false;
    }
    next();
});

// Pre-update middleware
userSchema.pre(['updateOne', 'findOneAndUpdate'], function() {
    this.set({ updatedAt: new Date() });
});

// Instance methods
userSchema.methods.updateLastLogin = async function() {
    this.lastLoginAt = new Date();
    this.loginCount += 1;
    return await this.save();
};

// Static methods
userSchema.statics.updateUserPreferences = async function(userId, preferences) {
    return await this.findByIdAndUpdate(
        userId,
        { $set: { preferences: preferences } },
        { new: true, runValidators: true }
    );
};

const User = mongoose.model('User', userSchema);

// Product schema with complex validation
const productSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true,
        minlength: 3,
        maxlength: 200
    },
    sku: {
        type: String,
        required: true,
        unique: true,
        uppercase: true,
        validate: {
            validator: function(sku) {
                return /^[A-Z0-9-]+$/.test(sku);
            },
            message: 'SKU must contain only uppercase letters, numbers, and hyphens'
        }
    },
    description: {
        type: String,
        maxlength: 2000
    },
    category: {
        type: String,
        required: true,
        enum: ['Electronics', 'Clothing', 'Books', 'Home', 'Sports']
    },
    price: {
        type: Number,
        required: true,
        min: [0.01, 'Price must be greater than 0'],
        validate: {
            validator: function(price) {
                return Number.isFinite(price) && price > 0;
            },
            message: 'Price must be a valid positive number'
        }
    },
    salePrice: {
        type: Number,
        validate: {
            validator: function(salePrice) {
                return !salePrice || salePrice < this.price;
            },
            message: 'Sale price must be less than regular price'
        }
    },
    inventory: {
        quantity: {
            type: Number,
            required: true,
            min: 0,
            default: 0
        },
        lowStockThreshold: {
            type: Number,
            default: 10
        },
        reserved: {
            type: Number,
            default: 0
        }
    },
    specifications: {
        type: Map,
        of: String
    },
    tags: [String],
    images: [String],
    status: {
        type: String,
        enum: ['active', 'inactive', 'discontinued'],
        default: 'active'
    },
    featured: {
        type: Boolean,
        default: false
    }
}, {
    timestamps: true
});

// Virtual for available quantity
productSchema.virtual('availableQuantity').get(function() {
    return this.inventory.quantity - this.inventory.reserved;
});

// Pre-save middleware for inventory alerts
productSchema.pre('save', function(next) {
    if (this.isModified('inventory.quantity') && 
        this.inventory.quantity <= this.inventory.lowStockThreshold) {
        console.log(`Low stock alert for product ${this.sku}: ${this.inventory.quantity} remaining`);
    }
    next();
});

const Product = mongoose.model('Product', productSchema);

module.exports = { User, Product };

Performing updates with Mongoose’s built-in methods

Mongoose provides several methods for updating documents, each with different use cases and return behaviors:

class MongooseUpdateManager {
    constructor() {
        this.User = User;
        this.Product = Product;
    }
    
    async updateUserProfile(userId, profileData) {
        try {
            // Method 1: findByIdAndUpdate - returns updated document
            const updatedUser = await this.User.findByIdAndUpdate(
                userId,
                { 
                    $set: {
                        'profile.bio': profileData.bio,
                        'profile.location.city': profileData.city,
                        'profile.location.country': profileData.country
                    }
                },
                {
                    new: true,              // Return updated document
                    runValidators: true,    // Run schema validators
                    select: '-__v'          // Exclude version key
                }
            );
            
            if (!updatedUser) {
                return {
                    success: false,
                    message: 'User not found'
                };
            }
            
            return {
                success: true,
                user: updatedUser,
                message: 'Profile updated successfully'
            };
            
        } catch (error) {
            if (error.name === 'ValidationError') {
                return {
                    success: false,
                    message: 'Validation failed',
                    errors: Object.values(error.errors).map(e => e.message)
                };
            }
            
            console.error('Error updating user profile:', error);
            return {
                success: false,
                message: 'An error occurred while updating the profile'
            };
        }
    }
    
    async updateUserEmail(userId, newEmail) {
        try {
            // Method 2: Using findById and save for middleware execution
            const user = await this.User.findById(userId);
            
            if (!user) {
                return {
                    success: false,
                    message: 'User not found'
                };
            }
            
            // Check if email is already taken
            const existingUser = await this.User.findOne({ 
                email: newEmail,
                _id: { $ne: userId }
            });
            
            if (existingUser) {
                return {
                    success: false,
                    message: 'Email address is already in use'
                };
            }
            
            // Update email and save (triggers pre-save middleware)
            user.email = newEmail;
            const savedUser = await user.save();
            
            return {
                success: true,
                user: savedUser,
                message: 'Email updated successfully. Please verify your new email address.'
            };
            
        } catch (error) {
            if (error.name === 'ValidationError') {
                return {
                    success: false,
                    message: 'Invalid email format'
                };
            }
            
            console.error('Error updating user email:', error);
            return {
                success: false,
                message: 'Failed to update email address'
            };
        }
    }
    
    async updateProductInventory(productId, quantityChange, operation = 'adjust') {
        try {
            const session = await mongoose.startSession();
            let result;
            
            await session.withTransaction(async () => {
                const product = await this.Product.findById(productId).session(session);
                
                if (!product) {
                    throw new Error('Product not found');
                }
                
                switch (operation) {
                    case 'adjust':
                        product.inventory.quantity += quantityChange;
                        break;
                    case 'set':
                        product.inventory.quantity = quantityChange;
                        break;
                    case 'reserve':
                        if (product.availableQuantity < quantityChange) {
                            throw new Error('Insufficient available inventory');
                        }
                        product.inventory.reserved += quantityChange;
                        break;
                    case 'release':
                        product.inventory.reserved = Math.max(0, product.inventory.reserved - quantityChange);
                        break;
                }
                
                if (product.inventory.quantity < 0) {
                    throw new Error('Inventory quantity cannot be negative');
                }
                
                result = await product.save({ session });
            });
            
            await session.endSession();
            
            return {
                success: true,
                product: result,
                message: `Inventory ${operation} completed successfully`
            };
            
        } catch (error) {
            console.error('Error updating product inventory:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    async bulkUpdateProducts(filter, updateData) {
        try {
            // Method 3: updateMany for bulk operations
            const result = await this.Product.updateMany(
                filter,
                updateData,
                { runValidators: true }
            );
            
            return {
                success: true,
                matched: result.matchedCount,
                modified: result.modifiedCount,
                message: `Updated ${result.modifiedCount} products`
            };
            
        } catch (error) {
            console.error('Error in bulk product update:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    async updateProductWithHistory(productId, updates, reason) {
        try {
            const session = await mongoose.startSession();
            let result;
            
            await session.withTransaction(async () => {
                // Get current product state
                const currentProduct = await this.Product.findById(productId).session(session);
                
                if (!currentProduct) {
                    throw new Error('Product not found');
                }
                
                // Create history record
                const historyRecord = {
                    productId: currentProduct._id,
                    changes: {},
                    reason: reason,
                    timestamp: new Date(),
                    previousValues: {}
                };
                
                // Track changes
                for (const [field, newValue] of Object.entries(updates)) {
                    const currentValue = this.getNestedValue(currentProduct, field);
                    if (currentValue !== newValue) {
                        historyRecord.changes[field] = newValue;
                        historyRecord.previousValues[field] = currentValue;
                    }
                }
                
                // Update product
                result = await this.Product.findByIdAndUpdate(
                    productId,
                    { $set: updates },
                    { 
                        new: true, 
                        runValidators: true, 
                        session 
                    }
                );
                
                // Save history record (you'd have a separate ProductHistory model)
                // await ProductHistory.create([historyRecord], { session });
            });
            
            await session.endSession();
            
            return {
                success: true,
                product: result,
                message: 'Product updated with history tracking'
            };
            
        } catch (error) {
            console.error('Error updating product with history:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
    
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => current && current[key], obj);
    }
}

// Usage examples
async function demonstrateMongooseUpdates() {
    try {
        // Connect to MongoDB using Mongoose
        await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/updatedb');
        
        const updateManager = new MongooseUpdateManager();
        
        // Example 1: Update user profile
        const profileResult = await updateManager.updateUserProfile(
            '507f1f77bcf86cd799439011',
            {
                bio: 'Full-stack developer with 5+ years experience',
                city: 'San Francisco',
                country: 'USA'
            }
        );
        
        console.log('Profile update result:', profileResult);
        
        // Example 2: Update user email with validation
        const emailResult = await updateManager.updateUserEmail(
            '507f1f77bcf86cd799439011',
            '[email protected]'
        );
        
        console.log('Email update result:', emailResult);
        
        // Example 3: Update product inventory with transaction
        const inventoryResult = await updateManager.updateProductInventory(
            '507f1f77bcf86cd799439021',
            -5,  // Decrease quantity by 5
            'adjust'
        );
        
        console.log('Inventory update result:', inventoryResult);
        
        // Example 4: Bulk update products
        const bulkResult = await updateManager.bulkUpdateProducts(
            { category: 'Electronics', status: 'active' },
            { 
                $set: { 
                    featured: true,
                    'specifications.warranty': '2 years'
                },
                $inc: { 'inventory.quantity': 10 }
            }
        );
        
        console.log('Bulk update result:', bulkResult);
        
    } catch (error) {
        console.error('Mongoose update demonstration failed:', error);
    } finally {
        await mongoose.disconnect();
    }
}

Validating data before update operations

Mongoose provides powerful validation capabilities that automatically run during update operations:

// Advanced validation schemas
const advancedUserSchema = new mongoose.Schema({
    email: {
        type: String,
        required: true,
        unique: true,
        validate: [
            {
                validator: function(email) {
                    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
                },
                message: 'Invalid email format'
            },
            {
                validator: async function(email) {
                    // Async validation to check domain blacklist
                    const blacklistedDomains = ['tempmail.com', 'throwaway.email'];
                    const domain = email.split('@')[1];
                    return !blacklistedDomains.includes(domain);
                },
                message: 'Email domain is not allowed'
            }
        ]
    },
    age: {
        type: Number,
        min: [13, 'Must be at least 13 years old'],
        max: [120, 'Age cannot exceed 120'],
        validate: {
            validator: function(age) {
                return Number.isInteger(age);
            },
            message: 'Age must be a whole number'
        }
    },
    password: {
        type: String,
        required: true,
        minlength: [8, 'Password must be at least 8 characters'],
        validate: {
            validator: function(password) {
                // Strong password validation
                return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/.test(password);
            },
            message: 'Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character'
        }
    },
    profile: {
        socialMediaUrls: {
            type: [String],
            validate: {
                validator: function(urls) {
                    return urls.every(url => {
                        try {
                            new URL(url);
                            return true;
                        } catch {
                            return false;
                        }
                    });
                },
                message: 'All social media URLs must be valid'
            }
        },
        skills: {
            type: [String],
            validate: {
                validator: function(skills) {
                    return skills.length <= 20;
                },
                message: 'Cannot have more than 20 skills'
            }
        }
    }
});

class ValidationManager {
    constructor() {
        this.User = mongoose.model('AdvancedUser', advancedUserSchema);
    }
    
    async updateWithCustomValidation(userId, updateData, customValidations = {}) {
        try {
            // Custom pre-update validation
            const validationResult = await this.runCustomValidations(updateData, customValidations);
            
            if (!validationResult.isValid) {
                return {
                    success: false,
                    message: 'Custom validation failed',
                    errors: validationResult.errors
                };
            }
            
            // Perform update with Mongoose validation
            const updatedUser = await this.User.findByIdAndUpdate(
                userId,
                { $set: updateData },
                {
                    new: true,
                    runValidators: true,
                    context: 'query' // Ensures 'this' refers to query in validators
                }
            );
            
            if (!updatedUser) {
                return {
                    success: false,
                    message: 'User not found'
                };
            }
            
            return {
                success: true,
                user: updatedUser,
                message: 'User updated successfully'
            };
            
        } catch (error) {
            if (error.name === 'ValidationError') {
                const validationErrors = Object.values(error.errors).map(err => ({
                    field: err.path,
                    message: err.message,
                    value: err.value
                }));
                
                return {
                    success: false,
                    message: 'Validation failed',
                    errors: validationErrors
                };
            }
            
            if (error.code === 11000) {
                return {
                    success: false,
                    message: 'Email address is already in use'
                };
            }
            
            console.error('Error updating user:', error);
            return {
                success: false,
                message: 'An unexpected error occurred'
            };
        }
    }
    
    async runCustomValidations(data, customValidations) {
        const errors = [];
        
        // Business logic validations
        if (customValidations.checkEmailChange && data.email) {
            const isEmailChangeAllowed = await this.validateEmailChange(data.email);
            if (!isEmailChangeAllowed) {
                errors.push('Email changes are not allowed at this time');
            }
        }
        
        if (customValidations.checkProfileCompleteness && data.profile) {
            const requiredProfileFields = ['firstName', 'lastName', 'bio'];
            const missingFields = requiredProfileFields.filter(field => 
                !data.profile[field] || data.profile[field].trim() === ''
            );
            
            if (missingFields.length > 0) {
                errors.push(`Profile update requires: ${missingFields.join(', ')}`);
            }
        }
        
        if (customValidations.validateSkillsUnique && data.profile?.skills) {
            const uniqueSkills = [...new Set(data.profile.skills)];
            if (uniqueSkills.length !== data.profile.skills.length) {
                errors.push('Skills must be unique');
            }
        }
        
        return {
            isValid: errors.length === 0,
            errors: errors
        };
    }
    
    async validateEmailChange(newEmail) {
        // Custom business logic for email changes
        // For example, check if user has changed email recently
        const user = await this.User.findOne({ email: newEmail });
        return !user; // Simple check - email doesn't exist
    }
    
    async conditionalUpdate(userId, updateData, conditions) {
        try {
            // Build conditional filter
            const filter = { _id: userId };
            
            if (conditions.requireEmailVerified) {
                filter.emailVerified = true;
            }
            
            if (conditions.requireActiveStatus) {
                filter.status = 'active';
            }
            
            if (conditions.minimumAge) {
                filter.age = { $gte: conditions.minimumAge };
            }
            
            // Attempt conditional update
            const result = await this.User.findOneAndUpdate(
                filter,
                { $set: updateData },
                {
                    new: true,
                    runValidators: true
                }
            );
            
            if (!result) {
                return {
                    success: false,
                    message: 'Update conditions not met or user not found'
                };
            }
            
            return {
                success: true,
                user: result,
                message: 'Conditional update completed successfully'
            };
            
        } catch (error) {
            console.error('Conditional update failed:', error);
            return {
                success: false,
                message: error.message
            };
        }
    }
}

Testing and Debugging Your MongoDB Update Code

Using console logs and try/catch for debugging

Effective debugging is crucial for identifying and resolving update operation issues:

class DebugManager {
    constructor(database) {
        this.db = database;
        this.debugMode = process.env.NODE_ENV === 'development';
    }
    
    async debugUpdate(collection, filter, updateDoc, options = {}) {
        const debugInfo = {
            operation: 'update',
            collection: collection,
            timestamp: new Date(),
            filter: filter,
            updateDoc: updateDoc,
            options: options
        };
        
        try {
            if (this.debugMode) {
                console.log('🔍 Starting update operation:', {
                    collection: collection,
                    filter: JSON.stringify(filter, null, 2),
                    update: JSON.stringify(updateDoc, null, 2)
                });
            }
            
            const targetCollection = this.db.collection(collection);
            
            // Log query explanation in debug mode
            if (this.debugMode && options.explain) {
                const explanation = await targetCollection.find(filter).explain('executionStats');
                console.log('📊 Query explanation:', {
                    docsExamined: explanation.executionStats.totalDocsExamined,
                    docsReturned: explanation.executionStats.totalDocsReturned,
                    executionTime: explanation.executionStats.executionTimeMillis,
                    indexUsed: explanation.executionStats.executionStages?.indexName || 'No index'
                });
            }
            
            // Perform the update
            const startTime = Date.now();
            const result = await targetCollection.findOneAndUpdate(
                filter,
                updateDoc,
                {
                    returnDocument: 'after',
                    ...options
                }
            );
            const duration = Date.now() - startTime;
            
            debugInfo.result = {
                success: !!result.value,
                duration: duration,
                documentFound: !!result.value
            };
            
            if (this.debugMode) {
                console.log('✅ Update completed:', {
                    success: !!result.value,
                    duration: `${duration}ms`,
                    documentId: result.value?._id
                });
                
                if (result.value) {
                    console.log('📄 Updated document:', JSON.stringify(result.value, null, 2));
                } else {
                    console.log('⚠️ No document found matching filter');
                }
            }
            
            return {
                success: !!result.value,
                document: result.value,
                debugInfo: this.debugMode ? debugInfo : undefined
            };
            
        } catch (error) {
            debugInfo.error = {
                name: error.name,
                message: error.message,
                code: error.code,
                stack: this.debugMode ? error.stack : undefined
            };
            
            console.error('❌ Update operation failed:', {
                collection: collection,
                error: error.message,
                code: error.code,
                filter: JSON.stringify(filter)
            });
            
            if (this.debugMode) {
                console.error('🔍 Full error details:', error);
                console.error('📋 Debug info:', debugInfo);
            }
            
            // Provide helpful error messages
            let userFriendlyMessage = 'Update operation failed';
            
            if (error.code === 11000) {
                userFriendlyMessage = 'Duplicate key error - a document with this value already exists';
            } else if (error.name === 'CastError') {
                userFriendlyMessage = 'Invalid data type provided for update';
            } else if (error.name === 'ValidationError') {
                userFriendlyMessage = 'Data validation failed';
            }
            
            return {
                success: false,
                message: userFriendlyMessage,
                debugInfo: this.debugMode ? debugInfo : undefined
            };
        }
    }
    
    async bulkUpdateWithLogging(collection, updates, options = {}) {
        const batchSize = options.batchSize || 100;
        const results = {
            total: updates.length,
            successful: 0,
            failed: 0,
            errors: [],
            timing: {
                start: new Date(),
                batches: []
            }
        };
        
        console.log(`🚀 Starting bulk update: ${updates.length} operations in batches of ${batchSize}`);
        
        for (let i = 0; i < updates.length; i += batchSize) {
            const batch = updates.slice(i, i + batchSize);
            const batchNumber = Math.floor(i / batchSize) + 1;
            const batchStart = Date.now();
            
            console.log(`📦 Processing batch ${batchNumber}/${Math.ceil(updates.length / batchSize)} (${batch.length} operations)`);
            
            try {
                const targetCollection = this.db.collection(collection);
                const bulkOps = batch.map(update => ({
                    updateOne: {
                        filter: update.filter,
                        update: update.update,
                        upsert: update.upsert || false
                    }
                }));
                
                const batchResult = await targetCollection.bulkWrite(bulkOps, {
                    ordered: false
                });
                
                const batchDuration = Date.now() - batchStart;
                results.successful += batchResult.modifiedCount;
                results.timing.batches.push({
                    batch: batchNumber,
                    duration: batchDuration,
                    modified: batchResult.modifiedCount,
                    matched: batchResult.matchedCount
                });
                
                console.log(`✅ Batch ${batchNumber} completed: ${batchResult.modifiedCount} modified in ${batchDuration}ms`);
                
            } catch (error) {
                const batchDuration = Date.now() - batchStart;
                results.failed += batch.length;
                results.errors.push({
                    batch: batchNumber,
                    error: error.message,
                    duration: batchDuration
                });
                
                console.error(`❌ Batch ${batchNumber} failed: ${error.message}`);
                
                if (this.debugMode) {
                    console.error('🔍 Batch error details:', error);
                }
            }
        }
        
        results.timing.end = new Date();
        results.timing.total = results.timing.end - results.timing.start;
        
        console.log('📊 Bulk update summary:', {
            total: results.total,
            successful: results.successful,
            failed: results.failed,
            duration: `${results.timing.total}ms`,
            avgBatchTime: `${Math.round(results.timing.batches.reduce((sum, b) => sum + b.duration, 0) / results.timing.batches.length)}ms`
        });
        
        return results;
    }
    
    enableQueryProfiling() {
        if (this.debugMode) {
            // Enable MongoDB profiling for slow operations
            this.db.runCommand({
                profile: 2,
                slowms: 100,
                sampleRate: 1.0
            }).then(() => {
                console.log('📈 Query profiling enabled for operations > 100ms');
            }).catch(err => {
                console.warn('⚠️ Could not enable profiling:', err.message);
            });
        }
    }
    
    async getProfilingData() {
        if (this.debugMode) {
            try {
                const profilingData = await this.db.collection('system.profile')
                    .find({})
                    .sort({ ts: -1 })
                    .limit(10)
                    .toArray();
                
                console.log('📊 Recent slow operations:', profilingData.map(op => ({
                    operation: op.command,
                    duration: op.millis,
                    timestamp: op.ts
                })));
                
                return profilingData;
            } catch (error) {
                console.warn('⚠️ Could not retrieve profiling data:', error.message);
            }
        }
    }
}

// Usage example
async function demonstrateDebugging() {
    try {
        await dbConnection.connect();
        const db = dbConnection.getDatabase();
        const debugManager = new DebugManager(db);
        
        // Enable profiling
        debugManager.enableQueryProfiling();
        
        // Debug single update
        const singleResult = await debugManager.debugUpdate(
            'users',
            { email: '[email protected]' },
            { 
                $set: { 
                    lastLoginAt: new Date(),
                    'profile.loginCount': 1 
                }
            },
            { explain: true }
        );
        
        console.log('Single update result:', singleResult);
        
        // Debug bulk update
        const bulkUpdates = [
            {
                filter: { status: 'inactive' },
                update: { $set: { status: 'active', reactivatedAt: new Date() } }
            },
            {
                filter: { category: 'Electronics' },
                update: { $inc: { viewCount: 1 } }
            }
        ];
        
        const bulkResult = await debugManager.bulkUpdateWithLogging('products', bulkUpdates);
        console.log('Bulk update result:', bulkResult);
        
        // Get profiling data
        await debugManager.getProfilingData();
        
    } catch (error) {
        console.error('Debugging demonstration failed:', error);
    } finally {
        await dbConnection.disconnect();
    }
}

Recommended tools: Postman, MongoDB Compass, and Robo 3T

Professional MongoDB development requires the right tools for testing, monitoring, and debugging update operations:

MongoDB Compass - Official GUI Tool:

MongoDB Compass provides a visual interface for database operations and is essential for debugging update queries:

// Example of operations you can perform in Compass
const compassOperations = {
    // Visual query builder equivalent
    visualQuery: {
        filter: { status: 'active', age: { $gte: 18 } },
        update: { $set: { eligibleForPromo: true } },
        options: { multi: true }
    },
    
    // Performance insights
    performanceTab: {
        realTimeMetrics: true,
        slowQueries: true,
        indexUsage: true
    },
    
    // Schema analysis
    schemaAnalysis: {
        fieldTypes: true,
        valueDistribution: true,
        arrayLengths: true
    }
};

// Compass connection string format
const compassConnectionString = 'mongodb://username:password@localhost:27017/database?authSource=admin';

Postman for API Testing:

When testing update endpoints, Postman provides comprehensive request testing capabilities:

// Example API endpoint for testing in Postman
app.put('/api/users/:id', async (req, res) => {
    try {
        const userId = req.params.id;
        const updateData = req.body;
        
        // Log request for debugging
        console.log('🔍 Update request:', {
            userId,
            updateData,
            timestamp: new Date(),
            userAgent: req.get('User-Agent')
        });
        
        const result = await updateManager.updateUser(userId, updateData);
        
        // Log response for debugging
        console.log('📤 Update response:', {
            success: result.success,
            userId,
            timestamp: new Date()
        });
        
        if (result.success) {
            res.json({
                success: true,
                data: result.user,
                message: result.message
            });
        } else {
            res.status(400).json({
                success: false,
                message: result.message,
                errors: result.errors
            });
        }
        
    } catch (error) {
        console.error('❌ API error:', error);
        res.status(500).json({
            success: false,
            message: 'Internal server error'
        });
    }
});

// Postman test scripts
const postmanTests = `
// Test successful update
pm.test("Update successful", function () {
    pm.response.to.have.status(200);
    pm.expect(pm.response.json().success).to.be.true;
});

// Test response time
pm.test("Response time is less than 500ms", function () {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

// Test data structure
pm.test("Response has correct structure", function () {
    const jsonData = pm.response.json();
    pm.expect(jsonData).to.have.property('success');
    pm.expect(jsonData).to.have.property('data');
    pm.expect(jsonData.data).to.have.property('_id');
});

// Set variables for subsequent requests
pm.test("Set user ID for next request", function () {
    const jsonData = pm.response.json();
    pm.globals.set("userId", jsonData.data._id);
});
`;

Testing Framework Integration:

// Jest test example for update operations
const request = require('supertest');
const app = require('../app');

describe('User Update Operations', () => {
    let userId;
    
    beforeEach(async () => {
        // Create test user
        const response = await request(app)
            .post('/api/users')
            .send({
                email: '[email protected]',
                firstName: 'Test',
                lastName: 'User'
            });
        userId = response.body.data._id;
    });
    
    afterEach(async () => {
        // Clean up test data
        await request(app)
            .delete(`/api/users/${userId}`);
    });
    
    test('should update user profile successfully', async () => {
        const updateData = {
            firstName: 'Updated',
            profile: {
                bio: 'Updated bio'
            }
        };
        
        const response = await request(app)
            .put(`/api/users/${userId}`)
            .send(updateData)
            .expect(200);
        
        expect(response.body.success).toBe(true);
        expect(response.body.data.firstName).toBe('Updated');
        expect(response.body.data.profile.bio).toBe('Updated bio');
    });
    
    test('should handle validation errors', async () => {
        const invalidData = {
            email: 'invalid-email'
        };
        
        const response = await request(app)
            .put(`/api/users/${userId}`)
            .send(invalidData)
            .expect(400);
        
        expect(response.body.success).toBe(false);
        expect(response.body.message).toContain('validation');
    });
    
    test('should handle non-existent user', async () => {
        const fakeId = '507f1f77bcf86cd799439011';
        
        const response = await request(app)
            .put(`/api/users/${fakeId}`)
            .send({ firstName: 'Test' })
            .expect(404);
        
        expect(response.body.success).toBe(false);
        expect(response.body.message).toContain('not found');
    });
});

Security Tips for Updating Data

Sanitizing user input before database operations

Input sanitization is critical for preventing injection attacks and ensuring data integrity:

const validator = require('validator');
const mongoSanitize = require('express-mongo-sanitize');

class SecurityManager {
    constructor() {
        this.dangerousOperators = ['$where', '$regex', '$text', '$expr'];
        this.allowedUpdateOperators = ['$set', '$unset', '$inc', '$push', '$pull', '$addToSet'];
    }
    
    sanitizeInput(input) {
        if (typeof input === 'string') {
            // Remove potential NoSQL injection patterns
            const sanitized = input
                .replace(/\$\w+/g, '') // Remove $ operators
                .replace(/[{}]/g, '')  // Remove braces
                .trim();
            
            // Additional validation
            if (validator.contains(sanitized, 'javascript:')) {
                throw new Error('Invalid input detected');
            }
            
            return validator.escape(sanitized);
        }
        
        if (Array.isArray(input)) {
            return input.map(item => this.sanitizeInput(item));
        }
        
        if (input && typeof input === 'object') {
            const sanitized = {};
            for (const [key, value] of Object.entries(input)) {
                // Check for dangerous operators
                if (this.dangerousOperators.includes(key)) {
                    throw new Error(`Operator ${key} is not allowed`);
                }
                
                sanitized[this.sanitizeInput(key)] = this.sanitizeInput(value);
            }
            return sanitized;
        }
        
        return input;
    }
    
    validateUpdateDocument(updateDoc) {
        const errors = [];
        
        // Check for allowed operators only
        const operators = Object.keys(updateDoc);
        const invalidOperators = operators.filter(op => 
            op.startsWith('$') && !this.allowedUpdateOperators.includes(op)
        );
        
        if (invalidOperators.length > 0) {
            errors.push(`Invalid operators: ${invalidOperators.join(', ')}`);
        }
        
        // Validate $set operations
        if (updateDoc.$set) {
            for (const [field, value] of Object.entries(updateDoc.$set)) {
                if (field.includes('$') || field.includes('.') && !this.isValidNestedField(field)) {
                    errors.push(`Invalid field name: ${field}`);
                }
                
                if (typeof value === 'string' && value.length > 10000) {
                    errors.push(`Field ${field} exceeds maximum length`);
                }
            }
        }
        
        // Validate $inc operations
        if (updateDoc.$inc) {
            for (const [field, value] of Object.entries(updateDoc.$inc)) {
                if (typeof value !== 'number') {
                    errors.push(`$inc value for ${field} must be a number`);
                }
            }
        }
        
        return {
            isValid: errors.length === 0,
            errors: errors
        };
    }
    
    isValidNestedField(field) {
        // Allow specific nested field patterns
        const allowedPatterns = [
            /^profile\.\w+$/,
            /^settings\.\w+$/,
            /^inventory\.\w+$/,
            /^metadata\.\w+$/
        ];
        
        return allowedPatterns.some(pattern => pattern.test(field));
    }
    
    async secureUpdate(collection, filter, updateDoc, userPermissions = {}) {
        try {
            // Sanitize all inputs
            const sanitizedFilter = this.sanitizeInput(filter);
            const sanitizedUpdate = this.sanitizeInput(updateDoc);
            
            // Validate update document structure
            const validation = this.validateUpdateDocument(sanitizedUpdate);
            if (!validation.isValid) {
                return {
                    success: false,
                    message: 'Invalid update document',
                    errors: validation.errors
                };
            }
            
            // Check user permissions
            if (!this.checkUpdatePermissions(sanitizedUpdate, userPermissions)) {
                return {
                    success: false,
                    message: 'Insufficient permissions for this update'
                };
            }
            
            // Perform the secure update
            const targetCollection = this.db.collection(collection);
            const result = await targetCollection.findOneAndUpdate(
                sanitizedFilter,
                sanitizedUpdate,
                {
                    returnDocument: 'after',
                    projection: this.createSecureProjection(userPermissions)
                }
            );
            
            return {
                success: !!result.value,
                document: result.value,
                message: result.value ? 'Update completed securely' : 'Document not found'
            };
            
        } catch (error) {
            console.error('Secure update failed:', error);
            return {
                success: false,
                message: 'Security validation failed'
            };
        }
    }
    
    checkUpdatePermissions(updateDoc, permissions) {
        // Define field permission levels
        const fieldPermissions = {
            admin: ['status', 'role', 'permissions', 'systemFlags'],
            editor: ['content', 'description', 'category'],
            user: ['firstName', 'lastName', 'profile.bio', 'preferences']
        };
        
        const userLevel = permissions.level || 'user';
        const allowedFields = fieldPermissions[userLevel] || fieldPermissions.user;
        
        // Check $set operations
        if (updateDoc.$set) {
            for (const field of Object.keys(updateDoc.$set)) {
                if (!this.isFieldAllowed(field, allowedFields)) {
                    return false;
                }
            }
        }
        
        // Check other operations
        const restrictedOperations = ['$unset', '$inc'];
        for (const op of restrictedOperations) {
            if (updateDoc[op] && userLevel !== 'admin') {
                return false;
            }
        }
        
        return true;
    }
    
    isFieldAllowed(field, allowedFields) {
        return allowedFields.some(allowed => {
            if (allowed.endsWith('*')) {
                const prefix = allowed.slice(0, -1);
                return field.startsWith(prefix);
            }
            return field === allowed || field.startsWith(allowed + '.');
        });
    }
    
    createSecureProjection(permissions) {
        const sensitiveFields = ['password', 'apiKeys', 'internalNotes', 'systemFlags'];
        const projection = {};
        
        if (permissions.level !== 'admin') {
            sensitiveFields.forEach(field => {
                projection[field] = 0;
            });
        }
        
        return projection;
    }
}

// Express middleware for secure updates
function secureUpdateMiddleware(req, res, next) {
    try {
        const securityManager = new SecurityManager();
        
        // Sanitize request body
        req.body = securityManager.sanitizeInput(req.body);
        
        // Add security context
        req.securityContext = {
            userPermissions: {
                level: req.user?.role || 'user',
                userId: req.user?.id
            },
            sanitizer: securityManager
        };
        
        next();
    } catch (error) {
        res.status(400).json({
            success: false,
            message: 'Input validation failed'
        });
    }
}

// Usage in Express routes
app.use('/api/users/:id', secureUpdateMiddleware);

app.put('/api/users/:id', async (req, res) => {
    try {
        const userId = req.params.id;
        const updateData = req.body;
        const { userPermissions, sanitizer } = req.securityContext;
        
        // Additional authorization check
        if (userPermissions.level !== 'admin' && userPermissions.userId !== userId) {
            return res.status(403).json({
                success: false,
                message: 'Cannot update other users'
            });
        }
        
        const result = await sanitizer.secureUpdate(
            'users',
            { _id: new ObjectId(userId) },
            { $set: updateData },
            userPermissions
        );
        
        if (result.success) {
            res.json(result);
        } else {
            res.status(400).json(result);
        }
    } catch (error) {
        console.error('API update error:', error);
        res.status(500).json({
            success: false,
            message: 'Internal server error'
        });
    }
});

Using role-based access control to limit write access

Implementing robust role-based access control ensures that users can only perform updates they’re authorized to make:

class RoleBasedAccessControl {
    constructor() {
        this.roles = {
            guest: {
                permissions: [],
                restrictions: ['no_write_access']
            },
            user: {
                permissions: ['update_own_profile', 'update_own_preferences'],
                restrictions: ['cannot_change_role', 'cannot_access_admin_fields']
            },
            moderator: {
                permissions: ['update_own_profile', 'update_own_preferences', 'moderate_content', 'update_user_status'],
                restrictions: ['cannot_change_role', 'cannot_delete_users']
            },
            admin: {
                permissions: ['*'], // All permissions
                restrictions: []
            }
        };
        
        this.fieldPermissions = {
            // Public fields - all authenticated users can update their own
            public: [
                'firstName', 'lastName', 'profile.bio', 'profile.avatar',
                'preferences.theme', 'preferences.language', 'preferences.notifications'
            ],
            
            // Protected fields - only moderators and admins
            protected: [
                'status', 'emailVerified', 'permissions', 'roles'
            ],
            
            // System fields - only admins
            system: [
                'role', 'systemFlags', 'internalNotes', 'createdAt', 'deletedAt'
            ]
        };
    }
    
    checkPermission(userRole, action, resourceOwnerId = null, currentUserId = null) {
        const roleConfig = this.roles[userRole];
        
        if (!roleConfig) {
            return { allowed: false, reason: 'Invalid role' };
        }
        
        // Check if user has global permissions
        if (roleConfig.permissions.includes('*')) {
            return { allowed: true };
        }
        
        // Check specific permission
        if (!roleConfig.permissions.includes(action)) {
            return { 
                allowed: false, 
                reason: `Role '${userRole}' does not have permission '${action}'` 
            };
        }
        
        // Check ownership for user-specific actions
        if (action.includes('own') && resourceOwnerId && currentUserId) {
            if (resourceOwnerId !== currentUserId) {
                return { 
                    allowed: false, 
                    reason: 'Can only perform this action on own resources' 
                };
            }
        }
        
        return { allowed: true };
    }
    
    validateFieldAccess(userRole, fields, isOwnResource = false) {
        const errors = [];
        const roleConfig = this.roles[userRole];
        
        for (const field of fields) {
            let accessLevel = 'denied';
            
            // Determine field access level
            if (this.fieldPermissions.public.some(f => field.startsWith(f))) {
                accessLevel = isOwnResource ? 'allowed' : 'denied';
            } else if (this.fieldPermissions.protected.some(f => field.startsWith(f))) {
                accessLevel = ['moderator', 'admin'].includes(userRole) ? 'allowed' : 'denied';
            } else if (this.fieldPermissions.system.some(f => field.startsWith(f))) {
                accessLevel = userRole === 'admin' ? 'allowed' : 'denied';
            }
            
            if (accessLevel === 'denied') {
                errors.push(`Access denied for field: ${field}`);
            }
        }
        
        return {
            allowed: errors.length === 0,
            errors: errors
        };
    }
    
    async authorizedUpdate(collection, filter, updateDoc, userContext) {
        try {
            const { userId, role } = userContext;
            
            // Extract target resource ID from filter
            const targetResourceId = filter._id?.toString() || filter.userId?.toString();
            const isOwnResource = targetResourceId === userId;
            
            // Check basic update permission
            const action = isOwnResource ? 'update_own_profile' : 'update_user_profile';
            const permissionCheck = this.checkPermission(role, action, targetResourceId, userId);
            
            if (!permissionCheck.allowed) {
                return {
                    success: false,
                    message: permissionCheck.reason
                };
            }
            
            // Extract fields being updated
            const fieldsBeingUpdated = this.extractUpdateFields(updateDoc);
            
            // Validate field-level access
            const fieldAccess = this.validateFieldAccess(role, fieldsBeingUpdated, isOwnResource);
            
            if (!fieldAccess.allowed) {
                return {
                    success: false,
                    message: 'Field access denied',
                    errors: fieldAccess.errors
                };
            }
            
            // Add audit trail
            const auditUpdate = {
                ...updateDoc,
                $set: {
                    ...updateDoc.$set,
                    updatedAt: new Date(),
                    lastUpdatedBy: userId
                }
            };
            
            // Perform the authorized update
            const targetCollection = this.db.collection(collection);
            const result = await targetCollection.findOneAndUpdate(
                filter,
                auditUpdate,
                {
                    returnDocument: 'after',
                    projection: this.createRoleBasedProjection(role)
                }
            );
            
            // Log the action for audit purposes
            await this.logUpdateAction(userId, role, collection, filter, fieldsBeingUpdated);
            
            return {
                success: !!result.value,
                document: result.value,
                message: result.value ? 'Update authorized and completed' : 'Resource not found'
            };
            
        } catch (error) {
            console.error('Authorized update failed:', error);
            return {
                success: false,
                message: 'Authorization check failed'
            };
        }
    }
    
    extractUpdateFields(updateDoc) {
        const fields = [];
        
        // Extract from $set operations
        if (updateDoc.$set) {
            fields.push(...Object.keys(updateDoc.$set));
        }
        
        // Extract from $unset operations
        if (updateDoc.$unset) {
            fields.push(...Object.keys(updateDoc.$unset));
        }
        
        // Extract from $inc operations
        if (updateDoc.$inc) {
            fields.push(...Object.keys(updateDoc.$inc));
        }
        
        // Extract from array operations
        ['$push', '$pull', '$addToSet'].forEach(op => {
            if (updateDoc[op]) {
                fields.push(...Object.keys(updateDoc[op]));
            }
        });
        
        return fields;
    }
    
    createRoleBasedProjection(role) {
        const projection = {};
        
        // Always exclude sensitive system fields for non-admins
        if (role !== 'admin') {
            const sensitiveFields = [
                'password', 'apiKeys', 'internalNotes', 'systemFlags', 
                'securityTokens', 'resetTokens'
            ];
            
            sensitiveFields.forEach(field => {
                projection[field] = 0;
            });
        }
        
        // Exclude moderator fields for regular users
        if (role === 'user') {
            const moderatorFields = ['permissions', 'moderationNotes', 'flaggedContent'];
            moderatorFields.forEach(field => {
                projection[field] = 0;
            });
        }
        
        return projection;
    }
    
    async logUpdateAction(userId, role, collection, filter, fields) {
        try {
            const auditLog = {
                userId: userId,
                userRole: role,
                action: 'update',
                collection: collection,
                filter: filter,
                fieldsModified: fields,
                timestamp: new Date(),
                ipAddress: null, // Would be set from request context
                userAgent: null  // Would be set from request context
            };
            
            await this.db.collection('audit_logs').insertOne(auditLog);
        } catch (error) {
            console.error('Failed to log update action:', error);
            // Don't fail the main operation if logging fails
        }
    }
    
    async bulkAuthorizedUpdate(collection, updates, userContext) {
        const results = {
            successful: [],
            failed: [],
            summary: {
                total: updates.length,
                successful: 0,
                failed: 0
            }
        };
        
        for (const update of updates) {
            try {
                const result = await this.authorizedUpdate(
                    collection,
                    update.filter,
                    update.update,
                    userContext
                );
                
                if (result.success) {
                    results.successful.push({
                        filter: update.filter,
                        result: result
                    });
                } else {
                    results.failed.push({
                        filter: update.filter,
                        error: result.message
                    });
                }
                
            } catch (error) {
                results.failed.push({
                    filter: update.filter,
                    error: error.message
                });
            }
        }
        
        results.summary.successful = results.successful.length;
        results.summary.failed = results.failed.length;
        
        return results;
    }
}

// Express middleware for role-based access control
function rbacMiddleware(requiredAction) {
    return async (req, res, next) => {
        try {
            const rbac = new RoleBasedAccessControl();
            const userContext = {
                userId: req.user?.id,
                role: req.user?.role || 'guest'
            };
            
            // Check if user has required permission
            const permissionCheck = rbac.checkPermission(
                userContext.role,
                requiredAction,
                req.params.id,
                userContext.userId
            );
            
            if (!permissionCheck.allowed) {
                return res.status(403).json({
                    success: false,
                    message: permissionCheck.reason
                });
            }
            
            // Add RBAC context to request
            req.rbac = rbac;
            req.userContext = userContext;
            
            next();
        } catch (error) {
            console.error('RBAC middleware error:', error);
            res.status(500).json({
                success: false,
                message: 'Authorization check failed'
            });
        }
    };
}

// Usage in Express routes with RBAC
app.put('/api/users/:id', 
    authenticateUser,
    rbacMiddleware('update_own_profile'),
    async (req, res) => {
        try {
            const userId = req.params.id;
            const updateData = req.body;
            
            const result = await req.rbac.authorizedUpdate(
                'users',
                { _id: new ObjectId(userId) },
                { $set: updateData },
                req.userContext
            );
            
            if (result.success) {
                res.json(result);
            } else {
                res.status(403).json(result);
            }
            
        } catch (error) {
            console.error('User update error:', error);
            res.status(500).json({
                success: false,
                message: 'Internal server error'
            });
        }
    }
);

// Admin-only route for bulk user updates
app.put('/api/admin/users/bulk',
    authenticateUser,
    rbacMiddleware('update_user_profile'),
    async (req, res) => {
        try {
            const updates = req.body.updates;
            
            const result = await req.rbac.bulkAuthorizedUpdate(
                'users',
                updates,
                req.userContext
            );
            
            res.json(result);
            
        } catch (error) {
            console.error('Bulk update error:', error);
            res.status(500).json({
                success: false,
                message: 'Bulk update failed'
            });
        }
    }
);

Conclusion and Best Practices

Summarizing key takeaways for efficient updates

Mastering MongoDB update operations in Node.js requires understanding both the technical mechanics and the broader architectural considerations that ensure your applications remain performant, secure, and maintainable as they scale.

Technical Mastery: The foundation of effective MongoDB updates lies in understanding the different update methods and when to use each one. Use updateOne() for targeted single-document updates, updateMany() for bulk operations, and findOneAndUpdate() when you need to retrieve the updated document. Master MongoDB’s update operators—$set for field assignments, $inc for numeric incrementing, $push and $pull for array manipulation, and $unset for field removal.

Performance Optimization: Proper indexing is crucial for update performance. Create compound indexes that support your most common update filters, and use query explanations to verify index usage. For large datasets, implement batch processing with controlled memory usage, and consider using MongoDB’s bulkWrite() operations for optimal throughput.

Error Handling and Resilience: Implement comprehensive error handling that distinguishes between different failure types—validation errors, network timeouts, duplicate key violations, and permission denials. Use transactions for multi-document updates that require atomicity, and implement retry logic for transient failures.

Security Considerations: Always sanitize user input before database operations, implement field-level access controls based on user roles, and maintain audit trails for sensitive updates. Use schema validation to prevent invalid data from entering your database, and never trust client-side validation alone.

Checklist for clean and secure MongoDB update logic in Node.js apps

Before Implementation:

  •  Design your document schema with update patterns in mind
  •  Plan your indexing strategy to support common update filters
  •  Define user roles and field-level permissions
  •  Choose between native MongoDB driver and Mongoose based on project needs

During Development:

  •  Implement proper input validation and sanitization
  •  Use appropriate update operators instead of document replacement
  •  Handle edge cases like non-existent documents and validation failures
  •  Implement transaction support for multi-document operations
  •  Add comprehensive error handling with user-friendly messages
  •  Include audit logging for sensitive operations

Security Checklist:

  •  Sanitize all user inputs to prevent NoSQL injection
  •  Implement role-based access control for update operations
  •  Use field-level permissions to protect sensitive data
  •  Validate update documents to prevent dangerous operations
  •  Log all update operations for audit trails
  •  Use HTTPS for all database communications in production

Performance Optimization:

  •  Create indexes that support your update filters
  •  Use projections to limit returned data
  •  Implement batch processing for bulk operations
  •  Monitor query performance and optimize slow operations
  •  Use connection pooling for efficient resource utilization
  •  Consider read/write splitting for high-traffic applications

Testing and Monitoring:

  •  Write unit tests for all update operations
  •  Test edge cases and error conditions
  •  Implement integration tests for complex update workflows
  •  Set up monitoring for update operation performance
  •  Create alerts for high error rates or slow queries
  •  Regularly review audit logs for suspicious activity

Production Readiness:

  •  Implement proper connection management and retry logic
  •  Set up database backup and recovery procedures
  •  Configure monitoring and alerting for database health
  •  Document all update operations and their expected behaviors
  •  Plan for database scaling and migration scenarios
  •  Establish incident response procedures for database issues

Comments (0)

Comment


Note: All Input Fields are required.