MongoDB transactions in Node.js using Mongoose

shucoll

Shuvam Shiwakoti

Posted on June 4, 2022

MongoDB transactions in Node.js using Mongoose

Mongodb transactions

“In a database management system, a transaction is a single unit of logic or work, sometimes made up of multiple operations.”
In simple terms, transactions are used in situations when we need to perform multiple tasks in the database, and we want either all tasks to be successful or none. We expect transactions to have ACID properties.

Transactions in MongoDB

Multi-document transactions in MongoDB enable us to tackle many complex and practical use cases where we want to update multiple documents by maintaining atomicity across all collections in the database.

Using MongoDB Transactions with Node.js

Here, I am using the Mongoose library to do this as it provides a very intuitive interface for transactions.

Let’s take the example of order creation for an e-commerce application. As an order is created, the stock of the corresponding products needs to be updated.

import mongoose from 'mongoose';

import catchAsync from '../utils/catchAsync.js';
import Order from '../models/orderModel.js';
import Product from '../models/productModel.js';

export const createOrder = catchAsync(async (req, res) => {
  const session = await mongoose.startSession();

  // Start the transaction
  session.startTransaction();

  try {
    // Make the order creation part of the transaction
    const orderDoc = await Order.create([req.body], { session });

    for (const item of orderDoc[0].orderItems) {
      const product = await Product.findById(item.product).session(session);

      if (product.stock - item.qty < 0) {
        throw new Error('Order quantity is more than stock');
      }

      const query = {
        $inc: { stock: -item.qty },
      };

      // Make the product update part of the transaction
      await Product.findByIdAndUpdate(item.product, query, {
        new: true,
        runValidators: true,
      }).session(session);
    }

    // If no error, commit the transaction and reflect changes in database
    await session.commitTransaction();

    res.status(201).json({
      status: 'success',
      data: orderDoc,
    });
  } catch (err) {
    // Abort the transaction if error occurred
    await session.abortTransaction();
    throw err;
  } finally {
    session.endSession();
  }
});

Enter fullscreen mode Exit fullscreen mode

We first create an order document and make it a part of the transaction. Then we update each product in the order with the updated stock values. If an error occurs in any of the product updations, the transaction is aborted. This ensures none of the products are updated and the order is also not created. This is a very typical use case for transactions.

Setting up replica set locally for transactions

If you try to execute the above code locally, it will give an error saying transactions are only available for replica sets. So what does this mean?

A catch with using transactions in MongoDB is that they can only be used with replica sets. If you are using a cloud-hosted database like MongoDB Atlas, it automatically sets up replica sets, so using transactions is not a problem. But in a local environment like your computer, this is not the case. You need to explicitly set up the MongoDB server a replica set for transactions to work.

First shut down the current MongoDB server

Log into the mongo process in the terminal

mongo
Enter fullscreen mode Exit fullscreen mode

Inside the mongo interface, switch to the admin and shut down the server

use admin
Enter fullscreen mode Exit fullscreen mode
db.shutdownServer()
Enter fullscreen mode Exit fullscreen mode
exit
Enter fullscreen mode Exit fullscreen mode

Now start a new mongod process with a replica set

This is for Mac systems. I haven't tried for Linux and Windows systems but I believe, if not the same, it is something very similar.

mongod --port 27017 --replSet rs0 --bind_ip localhost --config /usr/local/etc/mongod.conf --fork
Enter fullscreen mode Exit fullscreen mode

Log into the mongo interface again and initiate the replica set

mongo
Enter fullscreen mode Exit fullscreen mode
rs.initiate()
Enter fullscreen mode Exit fullscreen mode
exit
Enter fullscreen mode Exit fullscreen mode

With this, a replica set is set up locally on your computer and now you can perform MongoDB transactions.

To switch to normal mongodb server without replica set

Shut down the server same as above, then execute the command below.

mongod --config /usr/local/etc/mongod.conf --fork
Enter fullscreen mode Exit fullscreen mode

Conclusion

You can now create Node.js applications using MongoDB transactions and set up a replica set to develop your application locally. You can refer to this github repo I created for the blog. Transactions are a very vast topic in database management, and they have many use cases. I hope, with this, you now have a basic understanding of transactions in MongoDB and can get started with them.

💖 💪 🙅 🚩
shucoll
Shuvam Shiwakoti

Posted on June 4, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related