Browse documentation
MongoDB TutorialVerified with mongodb 7.5.0

MongoDB Transactions with the Node.js Driver

Use sessions and transactions for atomic multi-document changes, handle failures, and understand when MongoDB's single-document atomicity is enough.

Start with single-document atomicity

MongoDB writes to one document are atomic. If related values can be modeled in one bounded document, an update with operators often provides the consistency you need without a multi-document transaction.

Use a transaction when a business rule requires changes to multiple documents or collections to commit together. Transactions require a supported replica set or sharded deployment; a standalone local server does not provide them.

Prepare two accounts

Run this setup in a deployment that supports transactions:

use("mongog_tutorial");
const accounts = db.collection("accounts");

accounts.deleteMany({ tutorial: "transactions" });
accounts.insertMany([
  { owner: "Ada", balance: 500, tutorial: "transactions" },
  { owner: "Grace", balance: 300, tutorial: "transactions" },
]);

Transfer value in a transaction

The convenient transaction API retries certain transaction failures. Every operation must receive the same session:

const session = client.startSession();

try {
  await session.withTransaction(async () => {
    const debit = await accounts.updateOne(
      { owner: "Ada", balance: { $gte: 75 }, tutorial: "transactions" },
      { $inc: { balance: -75 } },
      { session },
    );

    if (debit.modifiedCount !== 1) {
      throw new Error("The source account has insufficient balance.");
    }

    await accounts.updateOne(
      { owner: "Grace", tutorial: "transactions" },
      { $inc: { balance: 75 } },
      { session },
    );
  });
} finally {
  await session.endSession();
}

accounts.find({ tutorial: "transactions" }).sort({ owner: 1 });

If the callback throws, the transaction aborts and neither balance change becomes visible. finally ensures the client session is released.

Expected result

Ada's balance decreases to 425 and Grace's increases to 375. The final query runs after the transaction finishes and returns both documents as a paged result.

Do not run parallel transaction operations

The Node.js driver does not support parallel operations within one transaction. Do not use Promise.all() for operations sharing a transaction session. Execute them in a clear sequence so errors and retries preserve the intended order.

The transaction callback can run more than once when the driver handles a transient error. Keep non-database side effects such as sending email, charging a payment provider, or publishing a message outside the retried callback unless they are independently idempotent.

Configure concerns deliberately

Read concern controls the consistency level of reads. Write concern controls the acknowledgment required from the deployment. Read preference controls where eligible reads are routed. Transaction options can override client defaults, but stronger guarantees may cost latency.

Use deployment-appropriate defaults unless the application has a specific consistency requirement you can explain and test.

Design for retries and unknown outcomes

Distributed systems can lose a response even after the server applied work. The driver labels retryable transaction conditions and the convenient API handles supported retries, but the application still owns its business semantics.

Give the operation a stable identity when a caller may retry it. For example, insert a transfer record with a unique transferId in the same transaction as the balance changes. A second attempt can detect that identity instead of applying the transfer twice.

const transfers = db.collection("account_transfers");
const transferId = "tutorial-transfer-001";

await session.withTransaction(async () => {
  const existing = await transfers.findOne({ transferId }, { session });
  if (existing) return;

  const debit = await accounts.updateOne(
    { owner: "Ada", balance: { $gte: 25 }, tutorial: "transactions" },
    { $inc: { balance: -25 } },
    { session },
  );
  if (debit.modifiedCount !== 1) throw new Error("Insufficient balance");

  await accounts.updateOne(
    { owner: "Grace", tutorial: "transactions" },
    { $inc: { balance: 25 } },
    { session },
  );
  await transfers.insertOne({ transferId, amount: 25, createdAt: ISODate() }, { session });
});

Create a unique index on transferId before relying on this pattern. The example focuses on transaction design; a production ledger should also define currency precision, account identity, and immutable audit records.

Keep transactions short

Transactions retain server resources and can encounter conflicts with concurrent writes. Read only the documents required for the decision, use supporting indexes, avoid user interaction inside the transaction, and commit promptly.

Do not use a transaction as a lock around an entire workflow. Gather external input before starting it and perform retry-safe side effects after commit.

Practice by running the stable transfer ID twice and confirming that balances change only once. Then deliberately throw after the debit and confirm neither balance changes. Finish by checking that the session closes in both success and error paths.

Common mistakes

  • Starting a transaction on a standalone server produces an error because transactions require a supported topology.
  • Forgetting { session } on one operation leaves that operation outside the transaction.
  • Using Promise.all() inside the transaction is unsupported by the driver.
  • Performing irreversible external side effects inside a callback that may be retried can duplicate those effects.
  • Using transactions to repair a poor document model adds complexity that embedding might avoid.
  • Keeping a transaction open while waiting for user input or an external API increases conflict and resource cost.
  • Retrying a business operation without a stable identity can duplicate work even when the database transaction itself is atomic.

Continue learning

Now connect the database concepts to the product with Connect MongoG to MongoDB. The official driver guide explains transaction APIs, options, and retry behavior.