Handling Counters with Firestore triggers

Back to blog

Firestore does not have built-in counters, so when you need to handle a counter in your application there's a variety of options you can go with. In this article we'll talk about how to handle counters using Firestore Triggers with the Firebase Node.js SDK.

In a rush? check the repo here

Cloud Firestore triggers

Firestore triggers are event-based Cloud Functions. Following the Firebase documentation explanation, this is the typical lifecycle of a Cloud Firestore function:

  1. Waits for changes to a particular document.
  2. Triggers when an event occurs and performs its tasks.
  3. Receives a data object that contains a snapshot of the data stored in the specified document. For onWrite or onUpdate events, the data object contains two snapshots that represent the data state before and after the triggering event.

These are the events that are available in Firestore:

  • onCreate: triggered when a document is written to for the first time.
  • onDelete: triggered when a document with data is deleted.
  • onUpdate: triggered when a document already exists and has any value changed.
  • onWrite: triggered when onCreate, onUpdate or onDelete is triggered.

Hands On

Let’s imagine we have two collections storing data about a city.

We will keep everything here as simple as possible.

The first collection will be called cities, and this is where we’ll store the counter:

{
  name: string
  residentsCounter: number
}

The second collection will be called residents:

{
  fullName: string
  cityId: string
}

The gotcha here is that we’ll store the resident’s city document id in the resident document. In the next section we’ll explain how would we use it to tell which city counter should be changed on creation or deletion.

Firestore Triggers

Now we will create the Firestore triggers that will handle the counters values. A Firestore trigger is essentially a Cloud Function for Firebase — a single-purpose JavaScript function that is executed only once when a specific event being watched is emitted.

First of all, you'll need to install firebase-functions and firebase-admin as dependencies.

You can just use our sample repository

firebase-functions is needed to create Cloud Functions and set up triggers

firebase-admin is needed to initialize the Firebase app and access Firestore.

// index.js
import functions from 'firebase-functions'

import admin from 'firebase-admin'
admin.initializeApp()

const db = admin.firestore()

Now let's code the actual Cloud Firestore Triggers.

onCreate

Here we say "increment the counter every time a resident is created”

// index.js
// ...

export const incrementResidentsCounterOnCreate = functions.firestore
  .document('/residents/{documentId}')
  .onCreate((residentSnapshot) => {
    // get the city document id from the resident document
    const { cityId } = residentSnapshot.data()

    // increment the counter in the city document 
    return db.doc(`/cities/${cityId}`).update({
      residentsCounter: admin.firestore.FieldValue.increment(1)
    })
  })

onDelete

Here we say "decrement the city residents every time a resident is deleted”

// index.js
// ...

export const decrementResidentsCounterOnDelete = functions.firestore
    .document('/residents/{documentId}')
    .onDelete((residentSnapshot) => {
        // get the city document id from the resident document
        const { cityId } = residentSnapshot.data()

        // decrement the counter in the city document 
        return db.doc(`/cities/${cityId}`).update({
            residentsCounter: admin.firestore.FieldValue.increment(-1)
        })
    })

That's it. Now our counters are ready to be used.

You can run and test it locally using the Firebase Emulator. Our sample repository has everything ready for you to try.

Or deploy those functions to your project following the next section.

How to deploy your function

If you haven't set up your login and initialized your project already, please follow the Firebase documentation to do so.

Then you'll just need one command to deploy your functions:

firebase deploy --only functions

This command will deploy all your Cloud Functions from scratch. However, if you only want to deploy your new Firestore Triggers, you can perform a selective deploy:

firebase deploy --only functions:incrementResidentsCounterOnCreate,functions:decrementResidentsCounterOnDelete

Or just deploy a function that has changes:

firebase deploy --only functions:incrementResidentsCounterOnCreate