Add Real-Time Updates to your Vue App

Troy Moreland
Level Up Coding
Published in
4 min readNov 10, 2020

--

Photo by Saffu on Unsplash

I’ve been using Cloud Firestore as my primary database for months now and have been very happy with it.

Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud Platform. Like Firebase Realtime Database, it keeps your data in sync across client apps through realtime listeners and offers offline support for mobile and web so you can build responsive apps that work regardless of network latency or Internet connectivity. Cloud Firestore also offers seamless integration with other Firebase and Google Cloud Platform products, including Cloud Functions.

Google’s description of Firestore is clear enough, but, for some reason, I have ignored the part about “keeps your data in sync across client apps through realtime listeners”. I think I ignored it because (a) I didn’t think I needed it and (b) I didn’t understand it.

Having grown up on SQL databases I am very comfortable with writing code to perform all database reads and writes. The concept of real-time updates just sounds like chaos! 😬

Not only is this not true but I will be using real-time updates from now on. Here’s how!

Traditional Queries

Until recently I was only pulling data from Firestore using traditional query methods. This is like using “SELECT” in an SQL statement. To retrieve data in the traditional manner, Firestore provides a get() method with several options:

  • Retrieve all documents in a collection

db.collection("cities").get()

  • Retrieve all documents of a collection group (subcollections across numerous collections)

db.collectionGroup("landmarks").get()

  • Retrieve select documents of a group (where clause)

db.collection("cities").where("capital", "==", true).get()

  • Retrieve a single document

db.collection("cities").doc("SF").get()

Real-Time Updates

Now for the cool part. To create a real-time listener, Firestore provides the onSnapshot() method. All you do is replace the get() method used in the above examples with the onSnapshot() method. For example:

db.collection("cities").onSnapshot()

Once invoked, your clients will automatically and immediately receive all changes to in-scope documents in Firestore. It really feels like magic when you see it the first time!

Implementation

One significant change to this project is that I’ve added the Firebase SDK to the clients (SPA, Cordova, Electron, etc.) to enable the real-time updates. Previously I would only use the Firebase Admin SDK on the server and provide APIs to the client. I don’t raise this as an issue, just a change in my typical setup.

As a result, we will need to initiate our Firebase connection with the client. I’m using Quasar Framework which provides “boot” files that run when the client is started.

Nothing fancy here. We are just connecting to Firebase and exporting the Firestore methods.

Everything else is handled in the Vuex store.

Let’s step through this example in more detail.

First of all, I’m importing my Axios and Firestore instances. Axios is used to make API calls to my server and Firestore, of course, is used to setup my real-time listeners.

In this scenario, I’m handling user documents. These documents will get stored in the “collection” state.

For mutations, I only need a way to individually add, update, and delete user documents. The SET_USER mutation will handle adds and updates while DELETE_USER mutation will handle deletes.

I do have getters in my project but I’ve excluded them here because they have no bearing on the topic.

I have a total of four actions. The first three, addUser() , updateUser() , and deleteUser() are calling my server API. Notice there are no “commit” statements? Strange, right?? 😕

The final action, bindUsers() , is our magical call to Firestore to setup our listener. As you can see, every change sent to the client has a type of either “added”, “modified”, or “removed”. With that, and the document itself, we can call our mutations.

We still need to call our bindUsers() action. I have a component for listing users so I call it in this component’s created() lifecycle method. The listener will remain active until it is detached with the unsubscribe() method or the client is closed.

NOTE: When you first initiate the listener it will iterate through all in-scope documents in Firestore. In other words, you don’t need to create an action to load your initial state.

Let’s think about this for a second…

  1. The end-user submits a new user
  2. The addUser() store action is called
  3. The client calls the POST endpoint on the server
  4. The server creates the new user document in Firestore
  5. Firestore notifies our client of the newly created user 😮
  6. The SET_USER mutation adds the user to the user collection state
  7. The UI is updated to show the newly created user 👏(slow clap)

You still aren’t impressed?? Ok, picture this:

I am using the app from my computer’s browser. A co-worker is using the native app on their phone. I add a new user as described above and I see the newly created user in the UI immediately. SO DOES MY CO-WORKER! No refresh required. WHAT?!?! ⭐️ ⭐️ ⭐️ ⭐️ ⭐️

--

--

Started career as a developer in the Marine Corps in 1991. Founded Identity Automation in 2004. Always learning. Always coding. What's next?