Getting Started with Firebase Cloud Firestore: Build a Vue Contact App

Yomi Eluwande
πŸ‘οΈ 3,331 views
πŸ’¬ comments

Sometime back in October, Google announced the beta release of Firebase's Cloud Firestore which is a fully-managed NoSQL document database for both mobile and web app development. Firestore was designed to easily store and sync your app's data at global scale.

Cloud Firestore includes features such as:

  • Documents and collections with powerful querying
  • iOS, Android, and Web SDKs with offline data access
  • Real-time data synchronization
  • Automatic, multi-region data replication with strong consistency
  • Node, Python, Go, and Java server SDKs

In this tutorial, I will demonstrate how to get started with Firebase Cloud Firestore and also highlight the differences between Cloud Firestore and the existing Firebase Realtime Database.

We'll be building a Contact Management app, it's a basic CRUD app and Cloud Firestore acts as the backend database. You'll be able to see all contacts, add a new contact and view an individual contact. The Contact Management app will be built using Vue.js.

A screenshot of the contact management app can be seen below and you can check out a live demo here.

To get started with the app, we'll be using vue-cli to quickly scaffold a Vue.js app and even more specifically, the webpack template will be used.

Open a terminal window and run the following command to install the vue-cli tool:

npm install -g vue-cli

Let's now create the Vue.js app, Run the following command in a working directory:

vue init webpack firestore

This will go ahead and create a folder titled firestore containing a working Vue.js app with features like Hot Module Reloading, Webpack and vue-loader for single file components, and routes. Navigate to the firestore folder and run the npm run dev command to see the app live at http://localhost:8080.

Setting up Cloud Firestore

To begin using Firestore, you'll need a Gmail account, once that's good to go, you can sign in to https://console.firebase.google.com and create a new project.

You should get a dashboard similar to the one below. This dashboard contains all of Firebase's other services that can be used in your application.

Click on "Add Firebase to your web app" and copy the config details specific to your project to a safe location as you'll be needing it later.

Under the Develop menu, click on the Database link and under the database tab, click β€œTry Firestore Beta” and β€œStart in Test Mode”. That should take you to a dashboard where you can view the database as it changes in real time.

With Firestore, we'll be dealing with documents and collections. Data is usually stored in documents, documents are stored in collections. A document may contain a collection and a collection may contain a document.

Vue.js and Firestore

Let's get back to coding. Open the components folder and create two JavaScript files in it; firebaseConfig.js and firebaseInit.js. The firebaseConfig.js file will contain the config values gotten from the Firebase dashboard, we'll be exporting so it can be used in other locations.

export default {
  apiKey: 'AIzaSyDr0-Mef6D1RZsD2NoBaPOwordhUW58MyU',
  authDomain: 'contacts-app-dca62.firebaseapp.com',
  databaseURL: 'https://contacts-app-dca62.firebaseio.com',
  projectId: 'contacts-app-dca62',
  storageBucket: 'contacts-app-dca62.appspot.com',
  messagingSenderId: '715354469790'
}

We have a Firebase config file, but we need to initialize it somewhere, we can do that in the firebaseInit.js file like this:

import firebase from 'firebase'
import 'firebase/firestore'
import firebaseConfig from './firebaseConfig'
const firebaseApp = firebase.initializeApp(firebaseConfig)
export default firebaseApp.firestore()

In the code block above, firebase and firestore were both imported, as well as the firebaseConfig.js file created earlier. The config file is used to initialize Firebase in the project and is also exported with the firestore function.

Let's create the different components that will be used for the app. We'll be needing Home.vue, NewContact.vue and ViewContact.vue components. You can go ahead and create these files in the components folder.

Before we edit each component, let's update the router file to accommodate these new components. Navigate to the router folder, open up the index.js file in it and edit it with the code block below:

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import ViewContact from '@/components/ViewContact'
import NewContact from '@/components/NewContact'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/add',
      name: 'new-contact',
      component: NewContact
    },
    {
      path: '/:person',
      name: 'view-contact',
      component: ViewContact
    }
  ]
})

In the code block above, we imported the components to be used for the different routes that will be used in the app. The Home component is responsible for serving the / route, the NewContact component is used to serve the /add route and the ViewContact component is used to serve the /:person route.

Now that the routes have been defined, we can begin editing the components. Let's start with the NewContact component. Open up the NewContact.vue file in the components folder. It's very common for .vue files to be divided into three sections; <template></template>, <script></script> and <style></style>. I'll highlight the code in each section before putting it all together at the end.

The <script></script> section contains the main JavaScript code and you can proceed to put in the following code block

  <script>
    import db from './firebaseInit'
    export default {
      name: 'new-contact',
      data () {
        return {
          firstname: null,
          lastname: null,
          emailaddress: null,
          phonenumber: null
        }
      },
      methods: {
        saveContact () {
          db.collection('contacts').add({
            firstname: this.firstname,
            lastname: this.lastname,
            emailaddress: this.emailaddress,
            phonenumber: this.phonenumber,
            slug: this.generateUUID()
          })
            .then(function (docRef) {
              console.log('Document written with ID: ', docRef.id)
            })
            .catch(function (error) {
              console.error('Error adding document: ', error)
            })
        },
        generateUUID () {
          let d = new Date().getTime()
          let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = (d + Math.random() * 16) % 16 | 0
            d = Math.floor(d / 16)
            return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
          })
          return uuid
        }
      }
    }
</script>

In the code block above, the firebaseInit.js file which was created earlier, is imported as db. In the methods object, there's the saveContact function which essentially saves the new contact to the Firestore DB. This line db.collection('contacts').add() is a way to add data to the Firestore DB, it's a way of saying add this data to the collection called contacts (or create one if it doesn't exist.). Cloud Firestore stores data in Documents, which are stored in Collections. Inside the .add() function, the contact details gotten from the form which will be created soon is being sent to Firestore along with a unique slug which is generated by the generateUUID function.

Next, we'll edit the <template></template> section and edit the HTML needed to create the form to add new contacts.

<template>

  <section class="container">
    <h1>Add New Contact</h1>

    <form @submit.prevent="saveContact">

      <div class="field">
        <label class="label">First Name</label>
        <div class="control">
          <input class="input" type="text" placeholder="First Name" v-model="firstname" required>
        </div>
      </div>

      <div class="field">
        <label class="label">Last Name</label>
        <div class="control">
          <input class="input" type="text" placeholder="Last Name" v-model="lastname" required>
        </div>
      </div>

      <div class="field">
        <label class="label">Email Address</label>
        <div class="control">
          <input class="input" type="email" placeholder="Email Address" v-model="emailaddress" required>
        </div>
      </div>

      <div class="field">
        <label class="label">Phone Number</label>
        <div class="control">
          <input class="input" type="text" placeholder="Phone Number" v-model="phonenumber" required>
        </div>
      </div>

      <div class="field">
        <div class="control">
          <button type="submit" class="button is-link">Submit</button>
        </div>
      </div>

    </form>
  </section>

</template>

In the code block above, we created the HTML form that adds a new contact to the Firestore DB. The value of each input tag is bound to the defined properties in the data() function via Vue.js' v-model.

The styling for this form was done in the <style></style section and can be seen below.

<style scoped>

  section {
    height: 100vh;
  }

  h1 {
    font-size: 30px;
    margin: 30px 0;
  }

  .input {
    height: 40px;
  }

</style>

Here's the NewContact.vue file in its whole entirety.

The next component we'll go through is the Home component. This component is used to serve the / route and it simply displays all the contacts in the Firestore DB and an optional button to view a contact individually.

Open up the Home.vue file and edit it with the content of the GitHub Gist below.

Let's go through the code block above, the div with a class of loader-section and conditional rendering v-if=loading is essentially a placeholder and acts as a loading state until the required data is successfully fetched from Firestore.

The div with a class of user-list and directive v-for=person in contacts is the UI needed to populate all the contacts from the data gotten from Firestore. We'll see how to query data from Firestore next.

In the <script></script> section of Home.vue, the first line of code is used to import the Firebase config just like we did in the NewContact component. In the created() function, the line db.collection('contacts').get() is used to get all data from the collection named contacts. The data is then pushed into the existing contacts array which was defined in the data() function.

One other thing to note in the code block above is the button/link that leads to the page where you can view an individual contact.

<router-link class="button is-primary" v-bind:to="{ name: 'view-contact', params: { person: person.slug }}">View Person</router-link>

The v-bind:to directive is used to dynamically bind the person segment to the contact's slug. This basically means that you use the slug to access an individual contact page, so something like this: http://localhost:8080/#/7099198a-5ec5-48ae-b8de-2befb5352f78 where the random string of characters is the slug. This was established in the routes file above where we did something like this:

{
      path: '/:person',
      name: 'view-contact',
      component: ViewContact
    }

The next and last component to be edited is the ViewContact component. Open up the ViewContact.vue file and edit it with the content of the GitHub Gist below:

Let's go through the code above. The <template></template> section contains the UI that's set to display the details of the contact. The various text interpolations in the template section are set to defined properties in the data() function in the <script></script> section.

The beforeRouteEnter() function is a navigation guard. Navigation guards are used to guard navigations either by redirecting it or canceling it. Navigation Guards may be resolved asynchronously, and the navigation is considered pending before all hooks have been resolved. That means until the guard has been resolved, users won't be able to visit the route in which the navigation guard is. It can be used to check if a user is authenticated in an app or some data has finished loading.

The beforeRouteEnter() function accepts three parameters, to, from, next.

  • to: the target Route Object being navigated to.

  • from: the current route being navigated away from.

  • next(): this function must be called to resolve the hook.

In this case, we are using the navigation guard to ensure that the data from Firestore has been successfully retrieved. So how exactly are we getting the data for a particular document in Firestore?

We use the .where() function to query for the data required as seen below:

db.collection('contacts').where('slug', '==', this.$route.params.person).get().then((querySnapshot) => {
      querySnapshot.forEach((doc) => {
        console.log(doc.id, ' => ', doc.data())
        this.firstname = doc.data().firstname
        this.lastname = doc.data().lastname
        this.emailaddress = doc.data().emailaddress
        this.phonenumber = doc.data().phonenumber
      })
})

The where() function takes in three parameters; an existing name of a field key in the Firestore DB (in this case, slug), the == sign, and the string in which to query the Firebase DB with (in this case, the $route.params.person string).

One more thing before running the app, we'll need to add the Bulma CSS framework to the project. You can do that by simply adding the line of code below to the index.html between the head tags.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css">

With that being done, you can run the app with the command npm run dev and you should the app live at http://localhost:8080

Differences and similarities between Cloud Firestore and Realtime Database

Now that we have seen how easy it is to get started with Firestore, it's time to ask questions like:

  • What's the difference between Cloud Firestore and Realtime Database?
  • How do I determine when to use Cloud Firestore or Realtime Database?
  • Will Realtime Database be deprecated?

There really is no difference between the two technologies, they both offer the same functionalities as seen here, the only advantage Cloud Firestore has, is in terms of scaling. Cloud Firestore was built in close collaboration with the Google Cloud Platform team and that means Cloud Firestore was built to scale right from the beginning.

Also, the Realtime Database is basically a JSON tree where you can store anything with no real structure or organization, Cloud Firestore is actually structured. Cloud Firestore is a document-model database, which means that all of your data is stored in objects called documents that consist of key-value pairs β€” and these values can contain any number of things, from strings to floats to binary data to JSON-y looking objects the team likes to call maps. These documents, in turn, are grouped into collections.

The Realtime Database may be used in cases where you are trying to optimize for cost and latency, and you can even go ahead to use both databases together. This also means Realtime Database is not going anywhere, both databases co-exist. It's also important to note that Cloud Firestore is in beta and it would be good to keep abreast of new developments so as to know if there will be any breaking changes.

Conclusion

In this tutorial, we saw how easy it is to get started with the new Cloud Firestore by Google and also touched on some points when comparing it to the already existing Realtime Database.

The codebase for the app we built above can be seen here. Feel free to play with it and build stuff. The live demo also exists here.