/ firebase

How to get the number of items in a Firestore collection

The answer to this question turns out to be not as straightforward as one might think because Firestore doesn't support aggregate queries at this moment, and in order to get the number of items in a collection the entire collection must be retrieved, which is far from optimal.

If you don't want to retrieve the entire collection every time you need to display the count, then you'll need to store and maintain it separately from the collection with the help of Cloud Functions. This is exactly what is suggested in the Aggregation Queries article.

However, the examples given in the article, where you would increment/decrement the counter on each write event, would not work as is because of the limitations of the firestore events. Namely, it's not guaranteed that the function will be executed only once for a given event and, as the article says, the functions must be idempotent (can be applied multiple times without changing the result beyond the initial application).

Considering that, the only reliable and relatively simple way of maintaining collection counts I've found so far is a cloud function that reads the size of the collection when a document is added or deleted and then stores it in a field.

Here is the code of such a function:

const functions = require('firebase-functions');
const admin = require('firebase-admin');

const db = admin.firestore();

exports.updateMyCollectionCount = functions.firestore
  .document(`users/{userId}/my-collection/{itemId}`)
  .onWrite(event => {

    const isUpdate = event.data.exists && event.data.previous.exists;

    if (isUpdate) {
      console.log('Skipping because it is an update operation.');
      return null;
    }

    const userRef = db
      .collection('users')
      .doc(event.params.userId);

    return db.runTransaction(t => {
      return t.get(userRef).then(userDoc => {
        return userDoc.ref.collection('my-collection').get().then(collectionSnapshot => {
          const newCount = collectionSnapshot.size;

          const updateData = {
            'my-collection-count': newCount
          };

          // If the `user` document alread exists, we need to use `update` operation, 
          // otherwise, if we use `set`, then all the data on the `user` document 
          // will be overriden. On the other hand, if the `user` document doesn't 
          // exist, we cannot use `update` because it requires the document to exist. 
          if (userDoc.exists) {
            t.update(userRef, updateData);
          }
          else {
            t.set(userRef, updateData);
          }

          console.log('Count updated successfully. New count is: ' + newCount);
        });
      });
    });
  });

This example assumes the following structure:

users/
  {userId}/
    my-collection/
      {itemId}
      ...
    my-collection-count
  ...

Here we have users collection, where for each user (userId) we have another collection called my-collection - this is the collection that we want to count items in and store the result in my-collection-count field for each user.

Compared to the example in the Aggregation Queries article, this function is idempotent because it calculates the count on each execution, so it doesn't matter how many times it is run. On the flip side it is less efficient, but in many cases it is an acceptable compromise.

See also this StackOverflow answer for more complex alternatives on how to make a function idempotent.

One more thing to be aware of is that in Firestore each document "costs" one read (see Firestore pricing). For example, if you have 100 items in a collection, you're being changed for 100 reads each time we update the count. So if you have a very large collection that is updated often, then you might consider alternative ways to maintain collection counts that don't require reading the entire collection.

Pavlo Glazkov

Pavlo Glazkov

Programmer. Full stack, with a focus on UI. JavaScript/TypeScript, Angular, Node.js, .NET

Read More