Transactions in Mongoose
If you haven’t already, import mongoose:
To create a transaction, you first need to create a session using or Mongoose#startSession
or .
// Using Mongoose's default connection
const session = await mongoose.startSession();
// Using custom connection
const db = await mongoose.createConnection(mongodbUri, { useUnifiedTopology: true, useNewUrlParser: true });
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 transient transaction error.
For more information on the ClientSession#withTransaction()
function, please see .
Mongoose’s Connection#transaction()
function is a wrapper around withTransaction()
that integrates Mongoose change tracking with transactions. For example, the Connection#transaction()
function handles resetting a document if you save()
that document in a transaction that later fails.
const schema = Schema({ name: String, arr: [String], arr2: [String] });
const Test = db.model('Test', schema);
await Test.createCollection();
let doc = await Test.create({ name: 'foo', arr: ['bar'], arr2: ['foo'] });
doc = await Test.findById(doc);
await db.
transaction(async (session) => {
doc.arr.pull('bar');
doc.arr2.push('bar');
await doc.save({ session });
doc.name = 'baz';
throw new Error('Oops');
catch(err => {
});
const changes = doc.getChanges();
assert.equal(changes.$set.name, 'baz');
assert.deepEqual(changes.$pullAll.arr, ['bar']);
assert.deepEqual(changes.$push.arr2, { $each: ['bar'] });
assert.ok(!changes.$set.arr2);
await doc.save({ session: null });
const newDoc = await Test.findById(doc);
assert.equal(newDoc.name, 'baz');
assert.deepEqual(newDoc.arr, []);
assert.deepEqual(newDoc.arr2, ['foo', 'bar']);
To get/set the session associated with a given document, use doc.$session()
.
The Model.aggregate()
function also supports transactions. Mongoose aggregations have a that sets the session
option. Below is an example of executing an aggregation within a transaction.
const Event = db.model('Event', new Schema({ createdAt: Date }), 'Event');
const session = await db.startSession();
await session.withTransaction(async () => {
await Event.insertMany([
{ createdAt: new Date('2018-06-01') },
{ createdAt: new Date('2018-06-02') },
{ createdAt: new Date('2017-06-01') },
{ createdAt: new Date('2017-05-31') }
], { session: session });
{
_id: {
month: { $month: '$createdAt' },
year: { $year: '$createdAt' }
},
count: { $sum: 1 }
}
},
{ $sort: { count: -1, '_id.year': -1, '_id.month': -1 } }
]).session(session);
assert.deepEqual(res, [
{ _id: { month: 6, year: 2018 }, count: 2 },
{ _id: { month: 6, year: 2017 }, count: 1 },
{ _id: { month: 5, year: 2017 }, count: 1 }
]);
});
session.endSession();
You can also use session.abortTransaction()
to abort a transaction:
const session = await Customer.startSession();
session.startTransaction();
await Customer.create([{ name: 'Test' }], { session: session });
await Customer.create([{ name: 'Test2' }], { session: session });
await session.abortTransaction();
const count = await Customer.countDocuments();
assert.strictEqual(count, 0);