Mongoose Transactions, the Basics

Best Practices

Error Handling: Always handle errors gracefully and ensure the session is ended.

Isolation: Transactions require a replica set or a sharded cluster with replica sets.

Session Scope: Avoid long-running sessions; end sessions promptly after use.


Transactions make complex operations reliable and consistent, minimizing data corruption risks in modern applications.

Mongoose Transactions and Rollbacks

Without transactions, any error in one operation wonโ€™t roll back previous operations, leading to inconsistent states.


    const mongoose = require('mongoose');

    async function withoutTransaction() {
      try {
    
        // Insert into Collection A
        const resultA = await CollectionA.create({ field: 'value' });
    
        // Insert into Collection B (fails)
        const resultB = await CollectionB.create({}); // Missing required fields, fails here
    
        console.log('Data inserted:', resultA, resultB);
      } catch (err) {
        console.error('Error occurred:', err);
      }
    }

            

The problem with the code above is if the second insert fails, the first operation remains committed, causing inconsistency.

The Solution to this is Transactions

With transactions, if any operation fails, all previous operations within the transaction are rolled back.

This ensures atomicity; either all operations succeed, or none are applied.

Starting a Transaction

To start a transaction, you need to create a session and then start the transaction.

Here's how you can do it:


    // you can start a transaction with the default connection by using mongoose
    const mongoose = require('mongoose');
    const session = await mongoose.startSession();

    // or you can start a transaction on a specific connection
    const conn = require("../models/connection"); // Database connection
    const session = await conn.startSession();

        

Example


    const conn = require("../models/connection"); // Database connection
    const Product = require("../models/product.model");
    const Inventory = require("../models/inventory.model");
    
    const createProductWithInventory = async () => {
        try {
            const session = await conn.startSession();
    
            await session.withTransaction(async () => {
                // Create the product
                const product = await Product.create(
                    [{
                      name: 'Smartphone',
                      price: 999,
                    },
                    ],{ session } // Include session
                     );
    
                // Create the inventory entry for the product
                await Inventory.create(
                    [
                        {
                            product_id: product[0]._id,
                            quantity: 100,
                        },
                    ],
                    { session } // Include session
                );
    
                console.log('Transaction successful');
            });
    
        } catch (error) {
            console.error('Transaction failed:', error);
        } finally {
            await session.endSession();
        }
    };

            

Components of a Transaction

startSession(): Initiates a session.

startTransaction(): Begins a transaction in the session.

commitTransaction(): Commits the transaction.

abortTransaction(): Rolls back the transaction.

When calling a "write" function on the Model, be sure to pass in the "session"

Managed Transactions (Automatic-Rollback/Commit)

MongoDB provides withTransaction for managed transactions, simplifying the workflow.

This method automatically handles commit/rollback.


    const mongoose = require("../models/connection"); // Connection
    const Customer = require("../models/customer.model");
    const Order = require("../models/order.model");
    
    const registerCustomerWithOrder = async () => {
        try {
            const session = await mongoose.startSession(); // Start a session
            await session.withTransaction(async () => {
                // Create a new customer
                const customer = await Customer.create(
                    [
                        {
                            name: 'John Doe',
                            email: 'john.doe@example.com',
                        },
                    ],
                    { session } // Include session
                );
    
                // Create an order for the new customer
                await Order.create(
                    [
                        {
                            customer_id: customer[0]._id, // Use the ID of the newly created customer
                            product: 'Smartphone',
                            quantity: 1,
                            total_price: 999,
                        },
                    ],
                    { session } // Include session
                );
    
                return customer;
            });

            console.log('Customer registered and order created successfully');
        } catch (error) {
            console.error('Error during transaction:', error);
        } finally {
            await session.endSession(); // End the session
        }
    };

            

Manually Managed Transactions

The outcome is the same but you control the commit/rollback which is often times more appropriate for various use cases.


    const conn = require("../models/connection"); 

    const example = async () => {
        const session = await conn.startSession();
    
        try {
            session.startTransaction();  
    
            await Model.create([{ /* payload */ }], { session });
    
            await Model.deleteOne({ /* conditions */ }, { session });
    
            await Model.updateOne({ /* conditions */ }, { /* payload */ }, { session } );
    
            await Model.findByIdAndUpdate(_id, { /* payload */  }, { session });
    
            const user = new Model( /* payload */);
            await user.save({ session });
            
            await session.commitTransaction();
             
        } catch (error) { 
            await session.abortTransaction();
        } finally {
            await session.endSession();
        }
    }

            

๐ŸŽ‰ Congrats โ€” Now you can use mongoose transactions!