Transactions in Mongoose

    If you haven’t already, import mongoose:

    To create a transaction, you first need to create a session using or or Connection#startSession().

    1. // Using Mongoose's default connection
    2. const session = await mongoose.startSession();
    3. // Using custom connection
    4. const db = await mongoose.createConnection(mongodbUri);
    5. const session = await db.startSession();
    • Creating a transaction
    • Committing the transaction if it succeeds
    • Aborting the transaction if your operation throws
    • Retrying in the event of a .

    For more information on the ClientSession#withTransaction() function, please see the MongoDB Node.js driver docs.

    Mongoose’s Connection#transaction() function is a wrapper around withTransaction() that integrates Mongoose change tracking with transactions. For example, suppose you save() a document in a transaction that later fails. The changes in that document are not persisted to MongoDB. The Connection#transaction() function informs Mongoose change tracking that the save() was rolled back, and marks all fields that were changed in the transaction as modified.

    1. const schema = Schema({ name: String, arr: [String], arr2: [String] });
    2. const Test = db.model('Test', schema);
    3. await Test.createCollection();
    4. let doc = await Test.create({ name: 'foo', arr: ['bar'], arr2: ['foo'] });
    5. doc = await Test.findById(doc);
    6. await db.
    7. transaction(async (session) => {
    8. doc.arr.pull('bar');
    9. doc.arr2.push('bar');
    10. await doc.save({ session });
    11. doc.name = 'baz';
    12. throw new Error('Oops');
    13. assert.equal(err.message, 'Oops');
    14. });
    15. const changes = doc.getChanges();
    16. assert.equal(changes.$set.name, 'baz');
    17. assert.deepEqual(changes.$pullAll.arr, ['bar']);
    18. assert.deepEqual(changes.$push.arr2, { $each: ['bar'] });
    19. assert.ok(!changes.$set.arr2);
    20. await doc.save({ session: null });
    21. const newDoc = await Test.findById(doc);
    22. assert.equal(newDoc.name, 'baz');
    23. assert.deepEqual(newDoc.arr, []);
    24. assert.deepEqual(newDoc.arr2, ['foo', 'bar']);

    To get/set the session associated with a given document, use .

    The Model.aggregate() function also supports transactions. Mongoose aggregations have a session() helper that sets the . Below is an example of executing an aggregation within a transaction.

    1. const Event = db.model('Event', new Schema({ createdAt: Date }), 'Event');
    2. const session = await db.startSession();
    3. await session.withTransaction(async () => {
    4. await Event.insertMany([
    5. { createdAt: new Date('2018-06-01') },
    6. { createdAt: new Date('2018-06-02') },
    7. { createdAt: new Date('2017-06-01') },
    8. { createdAt: new Date('2017-05-31') }
    9. ], { session: session });
    10. const res = await Event.aggregate([
    11. {
    12. _id: {
    13. month: { $month: '$createdAt' },
    14. year: { $year: '$createdAt' }
    15. },
    16. count: { $sum: 1 }
    17. }
    18. },
    19. { $sort: { count: -1, '_id.year': -1, '_id.month': -1 } }
    20. ]).session(session);
    21. assert.deepEqual(res, [
    22. { _id: { month: 6, year: 2018 }, count: 2 },
    23. { _id: { month: 6, year: 2017 }, count: 1 },
    24. { _id: { month: 5, year: 2017 }, count: 1 }
    25. ]);
    26. });
    27. session.endSession();

    You can also use session.abortTransaction() to abort a transaction:

    1. const session = await Customer.startSession();
    2. session.startTransaction();
    3. await Customer.create([{ name: 'Test' }], { session: session });
    4. await Customer.create([{ name: 'Test2' }], { session: session });
    5. await session.abortTransaction();
    6. const count = await Customer.countDocuments();
    7. assert.strictEqual(count, 0);