Firestore — Storing, Deleting, and Querying Data

Aditi Chowdhuri
7 min readFeb 18, 2024

--

Introduction

Firestore is a real-time NoSQL cloud database ideal for storing data for web applications. Therefore, whenever we need to retrieve an entire collection within our application, we can simply connect to our Firestore database to access it. Similarly, if we require a specific document, we can easily retrieve that as well.

Saving Data

To add data to the database from the frontend, we need to design a form to collect the data, accompanied by a submit button for data submission. In HTML, we proceed to construct this form, incorporating two text input fields and a submit button.

Form Creation

Currently, clicking on the submit button does not trigger any action, indicating the need to add some functionality. This requires navigating to the JavaScript file. The initial step involves obtaining a reference to the form we have just created, specifically by using the ID assigned to it.

Reference to the Form

To add data to Firestore, we first populate the form and then capture the submit event. This is done by adding an EventListener for the ‘submit’ event to the form. When the submit button is clicked or the Enter key is pressed, this listener triggers a callback function, which receives the event object ‘e’ (the name of this object is arbitrary). The immediate action within this function is to prevent the default form submission behavior, which typically reloads the page, using e.preventDefault(). This allows us to instead interact with Firestore.

The next step involves obtaining a reference to the desired collection in Firestore by using db.collection(), specifying the collection's name where the new document will be added. To add a document, we use the add method, of passing in an object that represents the new document. This object contains key-value pairs corresponding to the form inputs, which can be accessed by referencing the form's input fields by name to collect their values.

After successfully adding the document to Firestore, which we can do directly thanks to our setup with the Firebase SDK, we clear the form’s fields to prepare for potentially adding another document. This is accomplished by setting each input field’s value to an empty string.

For detailed instructions on setting up the database with Firebase, refer to my blog post on ‘Introducing Firestore for Web-Apps.’ Clearing the form fields after submission prepares the form for new entries without manual intervention.

Saving Data

Deleting Data

Now that we understand how to add documents to Firestore via a form, let’s explore how to delete documents from Firestore. For this purpose, we’ll create an element resembling a cross button and render it alongside each document. For detailed instructions on this process, you can refer to my blog ‘Introducing Firestore for Web-Apps.’ For every document we receive, we will create a li tag and render it to the DOM. Subsequently, we'll add a deletion element to each li tag. Initially, we create an div element named 'cross' and set its text content to 'x'. Then, we append this div to the li. To enable the deletion functionality for each cross button, we incorporate this feature within the render function. This allows us to set an event listener for each cross button. Upon accessing the newly created element, we add an event listener for click events. This listener triggers a callback function that receives the event object when the user clicks on it. The first action taken is to stop the event's propagation to prevent it from bubbling up, though this step is optional and the functionality remains unaffected without it. Next, we define a variable id and assign it the value of event.target.parentElement.getAttribute(), which is the unique ID of the document. With the ID in hand, we query Firestore to find and delete the document with this specific ID by navigating to the collection and using .doc(id).delete(). Upon pressing the cross button, the record is removed from the database, and refreshing the page will reflect its deletion from the frontend, demonstrating the process without real-time database updates.

Querying Data

Until now, when retrieving documents from Firestore, we have been doing so indiscriminately, meaning we have fetched every single document within the collection. However, there are occasions when querying a collection where we may not want to retrieve all documents but rather a subset based on the value of a specific field. To achieve this, we navigate to the point where we initially obtain the reference to all documents in the collection. Instead of using this as our final reference, we aim to refine our search within the collection to fetch only certain documents. This is accomplished by employing a method named .where() between our collection reference and the .get() method. We specify our criteria in .where() by defining three parameters: the field we wish to examine, the operator that determines how we assess this field, and the value the field should match. Thus, we effectively obtain references to documents within our collection that meet the specified condition, excluding all others. It's important to note that these conditions are case-sensitive, necessitating precision when comparing values. This method of querying is versatile and can be applied to various data types, such as retrieving users whose age is greater than 20.

Ordering Data

When retrieving data from Firestore, it may not be organized in any specific order. However, we can sort the data according to our requirements, such as alphabetically, by employing the .orderBy() method, which sorts the data based on a specific property within our dataset. It's important to note that in Firestore, capital letters are prioritized before lowercase letters. This method simplifies the process of ordering our data for retrieval, allowing us to systematically arrange it as needed.

We can further refine our query by adding a .where() method before .orderBy(), allowing us to filter the records based on a specified condition. Subsequently, the documents that meet this condition are then sorted alphabetically according to the specified property.

However, attempting to order a specific reference or collection of documents in a certain way for the first time can result in an error. This occurs because Firebase necessitates the creation of an index for the desired order of documents. Conveniently, this can be swiftly addressed by clicking on the link provided in the error message, which guides you through the index creation process.

Error

Upon clicking the link, you will be redirected to your Firebase console, where the index can be created automatically. Simply click on the ‘Create Index’ button to initiate the process.

Create Index

After clicking the button, Firestore will commence building the index. Following this, refreshing the page will confirm that the .where() method becomes functional as soon as the status changes to ‘Enabled’.

Enabled

During complex queries involving multiple conditions, such as .where(), .orderBy(), and others, Firestore may require specific indexes. Fortunately, there's no need for manual index creation; Firestore automatically prompts us in the console with a link to create any required index, simplifying the process.

Real-time Properties

Firestore offers real-time capabilities to automatically update the frontend as data is added or deleted. To leverage this, we set up a listener within our application to monitor our Firestore database for changes. Whether a document is added, deleted, or modified, we need to be alert to these changes to respond appropriately on the frontend. This is achieved using the onSnapshot method, which activates a function in response to any database modification, providing us with a snapshot of the updated data.

Upon initial application load, and while it’s the first time the database is being listened to, onSnapshot captures and treats the existing documents as newly added, allowing us to process these initial documents as well as any subsequent changes.

To implement this, we establish a reference to the database and the specific collection. With the .onSnapshot method, we specify that upon any alteration within the collection, a predefined callback function is triggered, receiving the current snapshot of the collection. Changes are identified through the .docChanges() method, which returns an array of objects detailing the changes. Each object represents a Firestore document, labeled with the type of change—such as 'added' or 'removed.'

For the initial load, Firestore labels documents as ‘added,’ enabling us to display them in the DOM immediately. We also stay vigilant for any new additions or deletions. By iterating over each change with the .forEach() method, we assess the type of modification. If a document is 'added,' we incorporate it into the DOM. Conversely, if a document is 'removed,' we locate the corresponding element by its 'data-id' attribute—matching the document's ID—and then remove it from the DOM.

Updating Data

To update data within a Firestore database, we first need to select the specific collection. To modify a document’s property, we must obtain a reference to that document using the .doc() method, where we pass the document's ID. For updating properties, the .update() method is employed, requiring an object as its argument. This object represents the document, allowing us to assign new values to its properties. Alternatively, the .set() method can be used to entirely replace a document with new properties. This approach overwrites all existing properties, meaning any property not included in the passed object will be lost. The key distinction between .update() and .set() lies in their impact: .update() modifies one or more specified properties without affecting the rest of the document, while .set() replaces the entire document, regardless of which properties are intended for update.

You can explore the complete code on my GitHub repository at https://aditi-chowdhuri.github.io/Firestore/. The README provides an overview of what the output resembles. Should you have any questions or need further clarification, please don’t hesitate to respond to this blog. I’m more than happy to assist.

--

--