Error Handling: Always handle errors gracefully and ensure the session is ended.
Isolation: Transactions require a replica set or a sharded cluster with replica sets.
Session Scope: Avoid long-running sessions; end sessions promptly after use.
Transactions make complex operations reliable and consistent, minimizing data corruption risks in modern applications.
Without transactions, any error in one operation wonโt roll back previous operations, leading to inconsistent states.
const mongoose = require('mongoose');
async function withoutTransaction() {
try {
// Insert into Collection A
const resultA = await CollectionA.create({ field: 'value' });
// Insert into Collection B (fails)
const resultB = await CollectionB.create({}); // Missing required fields, fails here
console.log('Data inserted:', resultA, resultB);
} catch (err) {
console.error('Error occurred:', err);
}
}
The problem with the code above is if the second insert fails, the first operation remains committed, causing inconsistency.
With transactions, if any operation fails, all previous operations within the transaction are rolled back.
This ensures atomicity; either all operations succeed, or none are applied.
To start a transaction, you need to create a session and then start the transaction.
Here's how you can do it:
// you can start a transaction with the default connection by using mongoose
const mongoose = require('mongoose');
const session = await mongoose.startSession();
// or you can start a transaction on a specific connection
const conn = require("../models/connection"); // Database connection
const session = await conn.startSession();
const conn = require("../models/connection"); // Database connection
const Product = require("../models/product.model");
const Inventory = require("../models/inventory.model");
const createProductWithInventory = async () => {
try {
const session = await conn.startSession();
await session.withTransaction(async () => {
// Create the product
const product = await Product.create(
[{
name: 'Smartphone',
price: 999,
},
],{ session } // Include session
);
// Create the inventory entry for the product
await Inventory.create(
[
{
product_id: product[0]._id,
quantity: 100,
},
],
{ session } // Include session
);
console.log('Transaction successful');
});
} catch (error) {
console.error('Transaction failed:', error);
} finally {
await session.endSession();
}
};
startSession(): Initiates a session.
startTransaction(): Begins a transaction in the session.
commitTransaction(): Commits the transaction.
abortTransaction(): Rolls back the transaction.
When calling a "write" function on the Model, be sure to pass in the "session"
MongoDB provides withTransaction for managed transactions, simplifying the workflow.
This method automatically handles commit/rollback.
const mongoose = require("../models/connection"); // Connection
const Customer = require("../models/customer.model");
const Order = require("../models/order.model");
const registerCustomerWithOrder = async () => {
try {
const session = await mongoose.startSession(); // Start a session
await session.withTransaction(async () => {
// Create a new customer
const customer = await Customer.create(
[
{
name: 'John Doe',
email: 'john.doe@example.com',
},
],
{ session } // Include session
);
// Create an order for the new customer
await Order.create(
[
{
customer_id: customer[0]._id, // Use the ID of the newly created customer
product: 'Smartphone',
quantity: 1,
total_price: 999,
},
],
{ session } // Include session
);
return customer;
});
console.log('Customer registered and order created successfully');
} catch (error) {
console.error('Error during transaction:', error);
} finally {
await session.endSession(); // End the session
}
};
The outcome is the same but you control the commit/rollback which is often times more appropriate for various use cases.
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();
} finally {
await session.endSession();
}
}
๐ Congrats โ Now you can use mongoose transactions!