What happens when multiple people are updating the same documents in your database? How do you ensure they’re not overwriting each other’s work?

What happens if 3 of them update the same property at (roughly) the same time? Will the updates go in the proper order? Will one overwrite the other?

Let’s look at a sales system for a moment. There’s one requirement we need to meet. We need to keep in a document the total number of sales on that day, and the amount sold, we can review end of the day.

If there have been 20 sales and six people add new sales, we don’t want any of those six sales to go missing. We want to properly update that number of sales property inside the document to always have the latest information.

For this, Firestore has a type of update called transactions.

A transaction works differently than a regular update. It goes like this:

  • Firestore runs the transaction.
  • You get the document ready to update whatever property you want to update.
  • Firestore checks if the document has changed. If not, you’re good, and your update goes through.
  • If the document has changed, let’s say a new update happened before yours did, then Firestore gets you the new version of the document and repeats the process until it finally updates the document.

The Source Code

Let’s create a function to go over the previous example. Let’s say we have our sales system, and every time a user makes a sale, we want to update a variable called totalSales in a document in Firestore.

First, let’s create an empty function called updateSalesNumer(), it doesn’t need any parameters for this example:

async updateSalesNumer() {
  try {
    // We'll add the logic here
  } catch (error) {
    console.log('Transaction failed: ', error);
    throw error;
  }
}

Right now, it’s not doing much. It will run the business logic inside a try/catch block in case something fails.

Next, we want to create the reference to the document we’ll update:

import { Firestore, doc } from '@angular/fire/firestore';

constructor(private readonly firestore: Firestore) {}

async updateSalesNumer() {
  try {
    const totalSalesDocRef = doc(this.firestore, `sales/--sales-information`);
  } catch (error) {
    console.log('Transaction failed: ', error);
    throw error;
  }
}

Notice that we’re importing two things from Firestore, the Firestore instance and the doc function. The Firestore instance is the instance we’re running of the database, and doc() will return the reference to the document we want to update.

Then we need to add the runTransaction() function to update the total number of sales.

import { Firestore, doc } from '@angular/fire/firestore';

constructor(private readonly firestore: Firestore) {}

async updateSalesNumer() {
  try {
    const totalSalesDocRef = doc(this.firestore, `sales/--sales-information`);

    await runTransaction(this.firestore, async (transaction) => {
      const salesInfoDoc = await transaction.get(totalSalesDocRef);
      const newTotalSales = salesInfoDoc.data().totalSales + 1;

      transaction.update(totalSalesDocRef, { totalSales: newTotalSales });
    });

  } catch (error) {
    console.log('Transaction failed: ', error);
    throw error;
  }
}

The runTransaction() function takes the Firestore instance and runs a transaction in the database. It will make sure you update the proper value.

That way, if ALL the salespeople make a sale at the same time, Firestore will start processing those transactions, and instead of overwriting each other’s work, it will update the proper values.