Open In App

Transactions in Mongoose

Last Updated : 26 Oct, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Mongoose Transactions allows to execute multiple database operations within a single transaction, ensuring that they are either all successful or none of them are. Mongoose provides powerful tools to manage transactions effectively.

Transactions in Mongoose

Step 1: First, make sure you have Mongoose installed and import it into your Node.js application:

import mongoose from 'mongoose';

Step 2: Create a Session, Transactions in Mongoose are managed through sessions. You can create a session using either the default Mongoose connection or a custom connection:

// Using the default Mongoose connection
const session = await mongoose.startSession();
// Using a custom connection
const db = await mongoose.createConnection(mongodbUri).asPromise();
const session = await db.startSession();

Step 3: Perform Transactions, Once you have a session, you can perform transactions using the session.withTransaction() helper or Mongoose’s Connection#transaction() function. These functions simplify the transaction management process.

let session = null;
return Customer.createCollection()
.then(() => Customer.startSession())
.then(_session => {
session = _session;
return session.withTransaction(() => {
return Customer.create([{ name: 'Test' }], { session: session });
});
})
.then(() => Customer.countDocuments())
.then(count => console.log(`Total customers: ${count}`))
.then(() => session.endSession());

Transactions with Mongoose Documents

Mongoose makes it easy to work with transactions when dealing with documents. If you retrieve a Mongoose document using a session, the document will automatically use that session for save operations. You can use doc.$session() to get or set the session associated with a specific document.

Example: In this example, we work with Mongoose documents within transactions:

  • We create a User model and initiate a session and a user document with the name ‘john’.
  • We start a transaction and use the session for queries, like finding the user with the name ‘john’.
  • We modify the user’s name to ‘smith’ and save the document and confirm that the updated document exists.
  • We commit the transaction and end the session.

Javascript




const UserModel = 
    db.model('User', new Schema({ name: String }));
  
let userSession = null;
return UserModel.createCollection()
    .then(() => db.startSession())
    .then(session => {
        userSession = session;
        const userToCreate = 
            UserModel.create({ name: 'john' });
        return userToCreate;
    })
    .then(() => {
        userSession.startTransaction();
        return UserModel
            .findOne({ name: 'john' }).session(userSession);
    })
    .then(user => {
        assert.ok(user.$session());
        user.name = 'smith';
        return user.save();
    })
    .then(() => UserModel.findOne({ name: 'smith' }))
    .then(result => {
        assert.ok(result);
        userSession.commitTransaction();
        userSession.endSession();
    });


Transactions with the Aggregation Framework

Mongoose’s Model.aggregate() function also supports transactions. You can use the session() helper to set the session option for aggregations within a transaction. This is useful when you need to perform complex aggregations as part of a transaction.

Example: In this example, we use transactions with the Mongoose Aggregation Framework:

  • We create an Event model and initiate a session.
  • We start a transaction and use the session to insert multiple documents into the Event collection.
  • We perform an aggregation operation using Event.aggregate() within the transaction.
  • We commit the transaction and end the session.

Javascript




const EventModel = db.model('Event',
    new Schema({ createdAt: Date }), 'Event');
  
let eventSession = null;
let insertManyResult = null;
let aggregationResult = null;
  
return EventModel.createCollection()
    .then(() => db.startSession())
    .then(session => {
        eventSession = session;
        eventSession.startTransaction();
  
        const documentsToInsert = [
            { createdAt: new Date('2018-06-01') },
            { createdAt: new Date('2018-06-02') },
            // ... other documents ...
        ];
  
        return EventModel.insertMany(documentsToInsert,
            { session: eventSession })
            .then(result => {
                insertManyResult = result;
            });
    })
    .then(() => EventModel.aggregate([
        // Your aggregation pipeline here
    ]).session(eventSession))
    .then(result => {
        aggregationResult = result;
        return eventSession.commitTransaction();
    })
    .then(() => eventSession.endSession())


Advanced Usage and Manual Transaction Control

For advanced users who require more control over when to commit or abort transactions, you can manually start a transaction using session.startTransaction(). This allows you to execute operations within and outside of the transaction as needed. You can also use session.abortTransaction() to manually abort a transaction if needed.

Example: This example demonstrates manual transaction control:

  • We create a Customer model and initiate a session.
  • We start a transaction and use the session for operations like creating a customer.
  • We verify that the customer is not found before committing the transaction.
  • We use the session for further queries, confirming the customer’s existence.
  • We commit the transaction and verify that the customer is still present after the commit. Finally, we end the session.

Javascript




const CustomerModel = db.model('Customer',
    new Schema({ name: String }));
  
let customerSession = null;
let createCustomerResult = null;
let findCustomerResult = null;
  
return CustomerModel.createCollection()
    .then(() => db.startSession())
    .then(session => {
        customerSession = session;
        customerSession.startTransaction();
  
        const customerToCreate = [{ name: 'Test' }];
  
        return CustomerModel.create(customerToCreate,
            { session: customerSession })
            .then(result => {
                createCustomerResult = result;
            });
    })
    .then(() => CustomerModel.findOne({ name: 'Test' }))
    .then(result => {
        findCustomerResult = result;
        assert.ok(!result);
    })
    .then(() => CustomerModel.findOne({ name: 'Test' })
        .session(customerSession))
    .then(doc => {
        assert.ok(doc);
    })
    .then(() => customerSession.commitTransaction())
    .then(() => CustomerModel.findOne({ name: 'Test' }))
    .then(result => {
        assert.ok(result);
    })
    .then(() => customerSession.endSession())
    .then(() => {
        // Now you can access 
        // createCustomerResult and findCustomerResult
        console.log('Create Customer Result:',
            createCustomerResult);
        console.log('Find Customer Result:',
            findCustomerResult);
    });


Conclusion: Transactions in Mongoose provide a powerful mechanism for ensuring data consistency and integrity in your MongoDB-based Node.js applications. By grouping multiple operations into atomic units, you can guarantee that your data remains in a reliable state, even in the face of errors or failures. Whether you’re working with documents, aggregations, or complex operations, Mongoose’s support for transactions makes it easier than ever to build robust and reliable applications on top of MongoDB.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads