Click Below to subscribe

How to implement Transactions in Mongoose & Node.Js (Express)

Transaction is a way to execute or commit a group of operations as a unit. In other words, it is a technique to call multiple SQL statements as a single unit.

In case of the transaction if any error occurred all the operations rollback.

Mongoose provides a very easy way to implement transactions.

# Without Transaction

const User = require("../models/user.model");
const ShippingAddress = require("../models/address.model");

const register = async () => {

    try {
        // Create User
        const user = await User.create({
            name: 'Van Helsing'
        });

        // Create Shipping Address
        const address = await ShippingAddress.create({
            address: 'Transylvania',
            user_id: user.id
        });
        console.log('success');
    } catch (error) {
        console.log('error');
    }
}

In the above example, we can face two error cases.

1.  user creation operation is not executed.

2.  user is created but the shipping address operation is not executed.

So to prevent this type of scenario we need to use transactions.

# Transactional Toolset

1. Creating a transaction

2. Rollback a transaction

3. Committing a transaction

# Implementation - 

models/connection.js

const mongoose = require('mongoose'); 

mongoose.connect('mongodb://localhost/my_db', {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

const conn = mongoose.connection;

conn.on('error', () => console.error.bind(console, 'connection error'));

conn.once('open', () => console.info('Connection to Database is successful'));

module.exports = conn;

1. Managed Transactions - Auto callback method

const conn = require("../models/connection"); 
const User = require("../models/user.model");
const ShippingAddress = require("../models/address.model");

const register = async () => {

    try {
        const session = await conn.startSession();                                   
        await session.withTransaction(async () => { 
    
            const user = await User.create([
                { 
                    name: 'Van Helsing' 
                }
            ], { session });
    
            await ShippingAddress.create([
                {
                    address: 'Transylvania',
                    user_id: user.id
                }
            ], { session });
    
            return user;
        });
        session.endSession();

        console.log('success');
    } catch (error) {
        console.log('error');
    }
}

Note:- that you must pass an array as the first parameter to create() if you want to specify options.

Ref:- https://mongoosejs.com/docs/api.html#model_Model.create

In this way, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the transaction method

2. Unmanaged Transactions - Manual method (Recommended)

In this way, we have more control over the operations. so I personally prefer this way to implement transactions. we have three methods for manually controlling the operations. 

session.startTransaction();        // for starting a new transaction
await session.commitTransaction(); // for committing all operations
await session.abortTransaction();  // for rollback the operations

const conn = require("../models/connection"); 
const User = require("../models/user.model");
const ShippingAddress = require("../models/address.model");
 
const register = async () => {

    const session = await conn.startSession();
    try {
        session.startTransaction();                    
        const user = await User.create([
            { 
                name: 'Van Helsing' 
            }
        ], { session });

        await ShippingAddress.create([
            {
                address: 'Transylvania',
                user_id: user.id
            }
        ], { session });
        await session.commitTransaction();
        
        console.log('success');
    } catch (error) {
        console.log('error');
        await session.abortTransaction();
    }
    session.endSession();
}

In the above example, we have full control over the operation so we can easily handle errors based on what error occurred.

Extras:-

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();
    }
    session.endSession();
}

Ref:- https://mongoosejs.com/docs/api/model.html

Leave Your Comment