KIEI-451: Introduction to Software Development

Storing and Retrieving Data with Firebase Cloud Firestore

Firebase is a platform developed by Google for rapid development of web and mobile applications. Firebase consists of several products, each for its own use-case. In this course, we'll be using the database – called Cloud Firestore – and the Authentication product, which will be used to help us with user authentication and login.

Note: Firebase also contains a product called Realtime Database – this is essentially the older database product that is being replaced by Cloud Firestore – we won't be using this.

The goal of Firebase Cloud Firestore – or any database, for that matter – is to allow for fast and efficient storage (writing) and retrieval (querying) of data. Let's take a deep dive into how to do these things with Firebase Cloud Firestore.

Using Firebase in a Web Application

Everything in Firebase is accomplished using their provided Firebase Client SDK (software development kit). We can find a reference on how to use these libraries in the Firebase Console (https://console.firebase.google.com/):

  1. Select a project
  2. Go to the "gear" icon -> Project Settings
  3. Under "Firebase SDK Snippet", select "CDN"
  4. Copy-and-paste this configuration into our project.

In the end, the script we'll have in our project will look something like this:

        const firebaseConfig = {
          apiKey: [YOUR VALUE],
          authDomain: [YOUR VALUE],
          databaseURL: [YOUR VALUE],
          projectId: [YOUR VALUE],
          storageBucket: [YOUR VALUE],
          messagingSenderId: [YOUR VALUE],
          appId: [YOUR VALUE]
        }
      

Of course, replace [YOUR VALUE] with the values from your Firebase configuration. Adding this snippet to our code sets up Firebase and makes it ready to use. The best part is that we can now fully access our Cloud Firestore database and other Firebase services – directly from our JavaScript code. The variable firebase is what we can use to access our Firebase instance. The next thing we'll want to do is to set a variable that allows us to access our Cloud Firestore database:

        let db = firebase.firestore()
      

Now, we can use db to perform operations on our Cloud Firestore database.

Key Concepts

Firebase Cloud Firestore is a document database – sometimes known as a NoSQL database. There are other document databases out there like MongoDB, Amazon DocumentDB; what they all have in common is that they provide a direct interface from the programming language being used (in our case, JavaScript) to the records (documents) contained in the database – rather than requiring SQL for communication like a relational database system (RDBMS) does. The vernacular is a little bit different too – instead of rows of data being stored in database tables, document database systems like Firebase store data in documents. Multiple documents of the same type are stored in collections.

Reading Data

In almost all databases, we typically want to retrieve data in one of four ways:

  1. Retrieve one document, if we know the unique identifier (ID) of that document
  2. Retrieve all documents in a collection
  3. Retrieve a subset of all documents in a collection, filtered on some criteria
  4. Put the resulting documents in a specific order

Retrieve One Document (when you know the Document ID)

        let docRef = await db.collection('items').doc('abcdef').get() // where 'abcdef' is the ID of an existing document in the 'items' collection
        
        let item = docRef.data()
        
        item.userName // => 'brian'
      

Here, we're using the .doc() method, passing it a document ID, to retrieve an existing record from our database. It's a lot like a fetch, in that we must use the await keyword to make this call, so that our code waits for the response from Firebase before continuing. The next part is a little strange, but we'll quickly get used to it – the call to Firebase returns a reference to a document, not the document itself. In order to read the document into a plain JavaScript object, we use the .data() method on the document reference.

Watch out! The value passed to the .doc() method MUST be a string. A common mistake is passing a numeric ID as an Integer.

Retrieve All Documents in a Collection

        let querySnapshot = await db.collection('items').get()
        
        querySnapshot.size // => 3
        
        let items = querySnapshot.docs

        for (let i=0; i<items.length; i++) {
          let id = items[i].id // get the document ID of the item for use later
          let item = items[i].data() // each member of the docs Array is a reference, so use .data() to get it into an Object
          item.name // => e.g. 'grapes'
        }
      

Here, we're using the same code as we used to get a specific record, but we're omitting the .doc() method. When we omit a request for a specific document, we get back a query snapshot from Firebase. We can ask a query snapshot for its size, which will tell us the number of documents that are returned. And we can ask a query snapshot for its docs, which is an Array of document references. As in the first example, we must call .data() on each reference, in order to get the actual data in Object format, so we can work with it.

Retrieve a Subset of Documents

        let querySnapshot = await db.collection('items').where('department', '==', 'produce').get()
        
        querySnapshot.size // => 2
        
        let items = querySnapshot.docs

        for (let i=0; i<items.length; i++) {
          let id = items[i].id // get the document ID of the item for use later
          let item = items[i].data() // each member of the docs Array is a reference, so use .data() to get it into an Object
          item.name // => e.g. 'grapes'
        }
      

Here, we're performing the same steps as before, with the addition of a filter which limits the results to only those in the produce department.

Ordering Documents

        let querySnapshot = await db.collection('items').orderBy('name').get()
        
        let items = querySnapshot.docs

        for (let i=0; i<items.length; i++) {
          let id = items[i].id // get the document ID of the item for use later
          let item = items[i].data() // each member of the docs Array is a reference, so use .data() to get it into an Object
          item.name // => e.g. 'grapes'
        }
      

This code will get all documents in the items collection, ordered by the name field.

Writing Data

There are a variety of ways to write documents to Cloud Firestore, but in all likelihood, you'll simply want to create (add) a new document, update an existing document, or delete an existing document. Here's how you do it:

Add a New Document

        let docRef = await db.collection('items').add({
          name: 'yogurt',
          department: 'dairy',
          quantity: 4
        })
      
        docRef.id // => the newly created document's ID
      

Update an Existing Document

        await db.collection('items').doc('abcdef').update({
          name: 'greek yogurt'
        }) // where 'abcdef' is the ID of an existing document in the 'items' collection
      

Set (create if doesn't exist, update if it does) an Existing Document

        await db.collection('items').doc('abcdef').set({
          name: 'greek yogurt'
        }) // where 'abcdef' is the ID of a new or existing document in the 'items' collection
      

Delete a Document

        await db.collection('items').doc('abcdef').delete() // where 'abcdef' is the ID of an existing document in the 'items' collection
      

Common Use-Cases: Timestamp and Incrementing Values

There are a few functions that Cloud Firestore provides for common use cases. Two things we'll commonly see in web applications are timestamping (i.e. capturing the time a document was created or updated) and incrementing a numeric value (think "likes" on a post, or "quantity" on an order line item). Here is a simple example of using these functions:

        let docRef = await db.collection('items').add({
          name: 'yogurt',
          department: 'dairy',
          quantity: 4,
          created: firebase.firestore.FieldValue.serverTimestamp()
        })
      
        await docRef.update({
          quantity: firebase.firestore.FieldValue.increment(10),
          updated: firebase.firestore.FieldValue.serverTimestamp()
        })
      

The first line will create a new item – just as we've done before – and will add a timestamp in the created field in the document. This is commonly done to keep track of the chronological order of things, for example, when a photo was posted or when an item was added to a shopping cart.

The second line updates the newly created document. Here, we're setting the value of updated to the server timestamp, once again. And we're asking Cloud Firestore to increment the value of quantity by 10.