skip to Main Content

Let’s say you’re counting the number of cities in a country, so you register an onDocumentCreated trigger for:

/countries/{countryId}/cities/{cityId}

In the trigger, you’ll want to increase the cityCount in the Country document at /countries/{countryId}. How should this be done?

The documentation states:

Collections and documents are created implicitly in Cloud Firestore.
Simply assign data to a document within a collection.
If either the collection or document does not exist, Cloud Firestore creates it.

My approach of running a transaction to update the count failed, because the parent document doesn’t exist at the moment the first city in that country was created.

const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {initializeApp, getApps} = require("firebase-admin/app");
const {getFirestore, DocumentReference} = require("firebase-admin/firestore");

if (!getApps().length) initializeApp();
const db = getFirestore();

exports.firestoreEventHander = onDocumentCreated(
    "countries/{countryId}/cities/{cityId}",
    async (event) => {
      const snapshot = event.data;
      console.log("snapshot exists:", snapshot.exists);  // true

      const countryRef = snapshot.ref.parent.parent;
      console.log("country path:", countryRef.path);  // countries/USA

      // Access snapshot through the db
      const cityDoc = await db.doc(`countries/${countryId}/cities/${cityId}`).get();
      console.log("cityDoc.exists:", cityDoc.exists);  // true

      // Check if the countryRef document exists
      const countryDoc = await countryRef.get();
      console.log("countryDoc:", countryDoc.data());  // ⚠️ undefined ⚠️
      console.log("countryDoc.exists:", countryDoc.exists);  // ⚠️ false ⚠️

      // Try to access the country via db.doc()
      const countryDoc2 = await db.doc(`countries/${countryId}`).get();
      console.log("countryDoc2:", countryDoc2.data());  // ⚠️ undefined ⚠️
      console.log("countryDoc2.exists:", countryDoc2.exists);  // ⚠️ false ⚠️
    },
);

The Trigger a function when a new document is created example doesn’t mention anything about the parent document of newly created nested documents not existing yet.

2

Answers


  1. Chosen as BEST ANSWER

    Apparently, and counter-intuitively to me in Firestore, "child documents are perfectly capable of existing with or without a parent document". While it is documented that after deleting a document, its subcollections are not automatically deleted (that's why recursiveDelete exists), it's not documented that creating a document may not create its parent.

    With that in mind, I've worked around this issue by replacing the update call with a set with { merge: true }. This will create the document if it doesn't exist.


  2. If you want to update a document inside a transaction, using the Firebase Admin SDK when you don’t already know if the document exists or not, there is only one main step to perform, with two sub-steps:

    1. Read the document using transaction.get()

    This is all easy to achieve using the transaction API. Any changes you make with the API will occur all at once or not at all. It doesn’t matter if the document previously exists or not.

    This procedure works regardless of the parent/child relationship of the documents. As you can imagine, child documents are perfectly capable of existing with or without a parent document. There is only a path string needed in order to read and write a document.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search