10.1 Reference - Firestore CRUD command templates
Tags: javascript, firestore, productivity, chatgpt
Published: 2022-02-27 10:16:00

Last Reviewed: Feb 2024 

### Introduction ###

Google's online documentation for Firestore CRUD (create, read, update, delete) instructions is very full but perhaps too verbose for everyday use. Here are templates for the most important variants of the Web Version 9 form of the Firestore library. If you find them useful, my suggestion is that you cut and paste them as is and then replace the word "my" in variable names by some suitable contraction of the name of the collection that you're targetting. For example, references to a collection called "Lecture_events"might be coded as "lecEvtsCollRef".

If you find these templates useful for your WebV9 work, you may quickly find yourself looking for something similar for Node.js. This will be because you've found the need to code some bit of your application in Firebase functions and have now realised that everything is entirely different here! I thought of writing a parallel "Firestore CRUD templates for Node.js" version of this post but have just (Jan 1 2023, appropriately) found this is now completely unnecessary. As of November 2022, we're firmly in the world of AI-assisted development and you can simply ask something like ChatGPT to take a bunch of code expressed in WebV9 form and convert it. Just start the AI up and give it a question like the following:

> *How would you rewrite the following firebase web version 9 javascript code for use in node.js?
    const myCollRef = collection(db, "myCollectionName");
    const myDocRef = doc(myCollRef);
    await setDoc(myDocRef, myDocData);*

I imagine (though I've not actually tried this) that you could use the same trick to get yourself versions for C, Python and all the other platforms that Google supports.

You might also be interested to now that there's a `ChatGPT Helper` VSCode extension that makes it easy to embed the submission of questions like this directly into the flow of your code development work 

Where will all this end?

### Creating documents ###

To create a document containing a myDocData object with an **automatically-generated** id:
```javascript
let myDocData =  .... create an object containing your data item properties .....
const myCollRef = collection(db, "myCollectionName");
const myDocRef = doc(myCollRef);
await setDoc(myDocRef, myDocData);
```

Note that, confusingly,  [Google Documentation](https://firebase.google.com/docs/firestore/manage-data/add-data) on 'Adding Data' references an `addDoc` function as an alternative to `setDoc`. See the <a href = "#1">Postscript</a> below for advice on why `setDoc`is preferred. 

In the code snippet above, the `myDocRef=` statement is the point at which an auto-id is allocated. If you need to find the value that's been assigned, you'll find this at `myDocRef.id`. Again, see the <a href = "#1">Postscript</a> below for further information on this point.

To create a document with a **data item** as its identifier :
```javascript
let myDocData =  .... create a data object  ..... 
let myDocumentIdentifier = .... create your identifier ....
const myDocRef = doc(db, "myCollectionName", myDocumentIdentifier)
await setDoc(myDocRef, myDocData);
```

### Reading documents ###

To retrieve an **individual** document using its document id:
```javascript
const myDocRef = doc(db, "myCollectionName", myDocId);
const myDoc = await getDoc(myDocRef);  
if (myDoc.exists()) {
  console.log("Document data:", myDoc.data());
} 
```  
To retrieve a **selection** of documents with selection and ordering criteria (example):
```javascript
const myCollRef = collection(db, "myCollectionName");
const myQuery = query(myCollRef, where("myField1Name", "==", myField1Value), orderBy("myField2Name", "asc"));
const mySnapshot = await getDocs(myQuery);
mySnapshot.forEach((myDoc) => {
  console.log(myDoc.id, " => ", myDoc.data());
});
```

Within a Snapshot's `forEach`, the data for a document is available as `myDoc.data()`, the document's docRef is `myDoc.ref` and its docId as `myDoc.id`. If you're just interested in determining the **existence** of document(s) that match the selection criteria, a useful trick is to check for non-zero `mySnapshot.size`.

If you want to refer to individual documents in the `snapshot` array, you'll find the data for the n'th entry at  `mySnapshot.docs[n].data()` and its id at `mySnapshot.docs[n].id`

Note that if you don't specify an `orderBy` field, documents will be returned in ascending order of docId. Also that if you specify more than one `where` field, you will need to create a (compound) index. The browser inspection tool will help you here - just follow the link embedded in the "index-needed" error message. Individual fields are indexed automatically in a Firestore database. 

To retrieve **all** of the documents in a collection:
```javascript
const myCollRef = collection(db, "myCollectionName");
const myQuery = query(myCollRef);
const mySnapshot = await getDocs(myQuery);
mySnapshot.forEach((myDoc) => {
  console.log(myDoc.id, " >= ", myDoc.data());
});
```
Firestore comparison operators are "==", ">" , "<", "<=", ">=" and "!=", plus some interesting [array membership](https://firebase.google.com/docs/firestore/query-data/queries) operators.

To retrieve **all** of the documents in a **hierarchy** of collections and **then** do something:

You have to be careful when you want to perform a certain action **after** processing on a multi-level hierarchy of collections has concluded. If your code contains a number of nested `foreach` statements, each containing  an `await` instruction, you can't rely on the individual `awaits` to tell you when the whole set has finished. Each of these individual `await`s occupies its own separate thread and these don't communicate directly with each other in any helpful way.

One way out of this problem is to use traditional `for` loop on your `snapshot`s rather than `forEach`s. Here's an example targeting all the children in a sub-collection before performing an action
```javascript
const myParentsCollRef = collection(db, "myParentCollectionName");
const myParentsQuery = query(myParentsCollRef);
const myParentsSnapshot = await getDocs(myParentsQuery);
for (let i = 0; i < myParentsSnapshot.size; i++) {
    let myParentDocId = myParentsSnapshot.docs[i].data()
    const myChildrenCollRef = collection(db, "myParentCollectionName", myParentDocId, "myChildrenCollectionName");
    const myChildrenQuery = query(myChildrenCollRef);
    const myChidrenSnapshot = await getDocs(myChildrenQuery);
    for (let j = 0; j < myParentsSnapshot.size; j++) {
        console.log(JSON.stringify(myChidrenSnapshot.docs[j].data()));
    }
}
```
Here, you can rely on your `await`s to be performed in strict sequence, and when you hit the end of the loop you know you can carry on confidently to perform your dependant action. But the performance hit created by this may be significant and so you might be interested in the following arrangement:

You can get a handle on the individual `promise`s launched by the `await`s in a  `forEach` loop by storing them in an array. You can then apply an `await Promise.all` instruction to this array to find out when all its member promises are done. It's not possible to provide a simple template here to suit all circumstances, but the following is a "sketch" that illustrates the broad principles. 

Here, a two-level hierarchy involving two separate collections (parents and children) are linked by a common parentsId field . The two collections are read into memory so that some sort of analysis can be performed on the aggregate. This can only be done when all the children have been read.

```javascript
const aggregateArray =[]
const parentsCollRef = collection(db, "parents");
const parentsQuery = query(parentsCollRef);
const parentsSnapshot = await getDocs(parentsQuery);

const promisesArray = [];
parentsSnapshot.forEach((parentsDoc) => {
  // for clarity, the nested awaits required to get the children for each parent are coded as an explicit function
promisesArray.push(fetchChildren((parentsDoc))  
})

// and here's the function itself
async function fetchChildren(parentsDoc) {
  const childrenCollRef = collection(db, "children");
  const childrenQuery = query(childrenCollRef, where("parentsId", "==", parentsDoc.data().parentsId));
  const childrenSnapshot = await getDocs(childrenQuery);
  chidrenSnapshot.forEach((childrenDoc) => {//push parent and children data into the aggregate array
  })
}

// and now you can perform your aggregate analysis. 
await Promise.all(promisesArray); 

```

### Updating a document ###

Example - to change the value of the myField property in a document's myDocData content
```javascript
    const myDocRef = doc(db, 'myCollectionName', myDocId);
    await setDoc(myDocRef, {myField: myFieldValue}, { merge: true });
```
Example - to replace the entire content of document myDocId with a new object containing only a myField property
```javascript
    const myDocRef = doc(db, 'myCollectionName', myDocId);
    await setDoc(myDocRef, {myField: myFieldValue}, { merge: false });
```
You can apply changes to a number of fields simultaneously by replacing the `{myField: myFieldValue}`bit in the above examples by an object containing the fields you want to change. 

### Deleting a document ###
```javascript
    const myDocRef = doc(db, 'myCollectionName', myDocId);
    await deleteDoc(myDocRef);
```

### CRUD operations within transactions ###

Inside a transaction, the patterns introduced above remain unchanged but the precise form of the setDoc commands are amended as follows:

Within the `runTransaction(db, async (transaction) => { ...   }).catch();` function:

* getDoc is replaced by transaction.get()
* setDoc is replaced by transaction.set()
* deleteDoc is replaced by transaction.delete()

<span id = '1'><h3>Postscript</h3></span>

1. As mentioned above, Google provides `addDoc()` and `updateDoc()` functions for document creation and update in parallel with `setDoc()`. But this seems unnecessarily confusing when setDoc can perform both operations. Also, when it comes to transactions, `addDoc()` can only be used for the creation of documents with auto ids. It seems simpler, in practice, just to use setDoc everywhere.

2. You may have noticed that there's no `await` on the `doc(myCollRef)` call that creates a Firestore document identifier. This tells you that Firestore somehow manages to do this without actually visiting the collection and seeing what is already in use. If you're curious about how it manages this you might like to check out the discussion at [StackOverflow](https://stackoverflow.com/questions/46618719/firestore-are-ids-unique-in-the-collection-or-globally).


If you have found this post interesting, you might find it useful to check out the [index](https://ngatesystems.com/waypointsindex) to this series

### Google documentation references ###

* Add data to Cloud Firestore : [https://firebase.google.com/docs/firestore/manage-data/add-data](https://firebase.google.com/docs/firestore/manage-data/add-data)

* Read data with Cloud Firestore : [https://firebase.google.com/docs/firestore/query-data/get-data](https://firebase.google.com/docs/firestore/query-data/get-data)

* Delete data from Cloud Firestore  : [https://firebase.google.com/docs/firestore/manage-data/delete-data](https://firebase.google.com/docs/firestore/manage-data/delete-data)

SDK documentation can be found at:
* https://firebase.google.com/docs/reference/js/firestore_ and
* https://firebase.google.com/docs/reference/js/firestore_.transaction