How To Create a Real Time To Do List App with Vue, Vuex & Firebase Tutorial

Hello! In this tutorial, we’ll be using Firebase and Vue to create a very simple project that will update your Firestore and your VueJS project in real time. We’re going to be creating a simple to do list before we get started – you need to have NodeJS & NPM installed.

Quick Note: I’m using Windows 10 and npm. The commands should be transferable between setups, you can also use Yarn instead of NPM. If you don’t have NodeJS and NPM installed prior to this, please visit this blog post by Treehouse for a detailed guide on how to do it.

Optional Step

I’ve added this project to Github, https://github.com/dwadedev/todolist-tutorial – you can clone the repo, fire up the local server and then read through the tutorial learning the steps afterward. If you do this, please skip ahead to step 4.2.1 and create your Firebase account and set up Firestore. You’ll only need to copy over your Firebase keys. The project will load a blank page until you add your Firebase information.

Simply run a git clone, in the project folder of your choice, follow the instructions in the Github readme and take it away!

1. Installing the Vue CLI

Open up your preferred command line tool, no need to worry about which directory you’re in as we’ll be installing this globally. I’ll be using Powershell for this tutorial as its a standard on Windows machines, you can use whichever command line tool you prefer. Please note: you may be required to run Powershell as an administrator depending on your system setup.

npm install -g @vue/cli

Once you’ve installed the Vue CLI (this can take a few minutes), verify it by typing the command below, this should return a version number. If not please go directly to the Vue CLI installation guide and follow their simple instructions / debugging methods.

vue -V

I’m running this on version 3.5.1.

2. Let’s begin!

Before we get started, we need to pick a project folder and create a blank Vue project. I’m going to be calling our project to-do-list as that’s what we’re creating today. I store all of my Vue projects in C:/sites/ for ease, so I’ll be running cd c:/sites/ but you can navigate to your preferred location and then run the Vue CLIs create command.

You can create the directory by running the following commands, or if you’re settled on where you want to put your project, skip this command block and jump to the next one.

cd c:/
mkdir sites
cd sites

Once you’re in your project folder, run the following command to get started:

vue create to-do-list

If the command runs successfully you’ll be greeted with 3 options. We’re going to manually select features, we need to install Vuex as we’ll be using its state management as part of this tutorial.

Use your arrow keys to navigate and press enter to select an option.

On the next list, we want to unselect Babel and select the following (using the space bar):-

  • Router
  • Vuex
  • Linter / Formatter
Our 3 selections!
Our 3 selections!

Press enter to confirm our selections and then you’ll be asked to configure the project. The answers you need are in the screenshot below, but let’s confirm the answers.

  • Use history mode for router?
    Yes
  • Pick a linter / formatter config:
    ESLint with error prevention only
  • Pick additional lint features:
    Lint on save
  • Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?
    In package.json (This is a personal preference, please do what you prefer)

Well done! Now press enter to begin the install. Depending on the speed of your machine, this can take a few minutes. On successful creation, you will be presented with a screen similar to this

Successfully created Vue Project
Successfully created Vue Project

Congrats! We’ve now got a default Vue project installed on our machine. Hopefully, your command line tool will be displaying the same as the image above. Vue CLI has told us our next steps, let’s enter our project directory and then fire up the local server.

cd to-do-list
npm run serve
Successfully started our local development server
Successfully started our local development server

3. Local Development Server

Default Vue Screen
Default Vue Screen

Providing the previous steps haven’t failed, you now have a locally running development environment for your VueJS project, amazing right? Open up the browser of your choice and go to either the local or the network URL and you should see the “Welcome to Your Vue.js App” splash screen.

It’s exciting, I know! Now we’ve done the necessary set up part, let’s get some structure to our app, clear out the things we don’t need and start some dev.

4. Dev time

Open up src/views/Home.vue in your code editor, I’ll be using VSCode for all coding… and let’s take a look inside. If you use VSCode, I’d recommend adding the Vetur extension

Quick Note: Set up your code editor to tab using spaces and for it to use 2 spaces as a tab. This will help you out as it is one of the most common linting errors.

We’ll try and keep this as simple as possible, I don’t want to focus on the best way to structure your apps, when to use components and when not to. I want you to come away from reading this confident in creating a real-time connection with your app and Firebase.

With that said, let’s remove the HelloWorld component and create a basic interface that we will be adding our items in to, we’ll keep the Vue logo (because it looks nice) but we don’t need anything else right now. I’ve also added some basic styling that we’ll need for the rest of the tutorial, so please include this!

<template>
  <div class="home">
    <img alt="Vue logo" src="@/assets/logo.png" id="vue-logo">
    <div class="title">What do I need to do today?</div>
    <input v-model="myTodo" /><button @click="addToDo">Add</button>
  </div>
</template>

<script>
export default {
  name: 'home',
  data: function () {
    return {
      myTodo: ''
    }
  },
  methods: {
    addToDo: function () {
      console.log('myTodo: ' + this.myTodo)
    }
  }
}
</script>

<style>
* {
  box-sizing:border-box;
}

body, html, #app {
  background:#8ac8e5;
}

.home {
  width:300px;
  margin:auto;
}

#vue-logo {
  width:100px;
}

input, button {
  border:0;
  width:100%;
  margin:0 0 10px;
  padding:7px;
}

input {
  font-size:12px;
}

button {
  background:#43b823;
  border:0;
  text-transform:uppercase;
  color:#fff;
  font-weight:700;
  cursor:pointer;
}

.title {
  font-size:14px;
  font-weight:700;
  padding:0 0 10px 0;
  margin:0 0 10px 0;
  border-bottom:1px solid #666;
}

#errors {
  background:#a52222;
  color:#fff;
  padding:5px;
}
</style>

4.1 Creating an add method

By this point, you should have your local server running, your slightly custom page visible in your browser and most importantly – no errors in the console! If we’re good to go. Please continue…

We want to detect when the user wants to add something, so let’s add a click event to the button.

<button @click="addTodo">Add</button>

We then need to update our script to include the addTodo method, we do this by creating a method’s object and adding the addToDo method to it, see below.

<script>
export default {
  name: 'home',
  data: function () {
    return {
      myTodo: ''
    }
  },
  methods: {
    addToDo: function () {
      console.log('myTodo: ' + this.myTodo)
    }
  }
}
</script>

 

Any time you press the Add button now, you should see a message logged in your console (Press F12 OR right click & inspect to open console), type anything in the input box, press enter and it’ll be logged.

4.2 Firebase Setup & Integration

Time to add the Firebase package to your Vue app, hop back into Powershell and stop your local dev server running (Ctrl+C) and install the Firebase package. Please note: you may still be required to run Powershell as an administrator at this point.

npm install --save firebase

Once the package has installed, type npm run serve again so we’re watching our files and our local server is still running. Nice one. You’ve stuck with it so far, so let’s get over to Firebase, create a project and get our credentials so we can get down to the real business!

Visit Firebase (https://console.firebase.google.com) /and login with your Google account. On the dashboard, select ‘Add a Project’ and fill it in like the following image. Obviously, adjust your locations depending on where you are in the world, but for a project like this – you don’t need to be too accurate.

Please Note: During testing a couple of users were greeted with more checkboxes
to agree to different terms, simply put – agree to it all as we’ll need our Firebase account.

4.2.1 Isn’t she lovely?

You’re now in your projects dashboard and oh, it is pretty. We’re only going to be using a few sections of Firebase for now, but I’m hoping to improve on that as these tutorials grow and grow.

Let’s get your Firebase project integrated into to your Vue project. Select the </> button at the top of the page, or in your project overview to register your app.

Open up src/main.js next and copy the code from the block below, we’ll need to replace the keys below with the ones in the pop-up, I would replace your entire main.js file with the contents below, simply filling in your keys.

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import firebase from 'firebase'

Vue.config.productionTip = false

const config = {
  apiKey: '',
  authDomain: '',
  databaseURL: '',
  projectId: '',
  storageBucket: '',
  messagingSenderId: ''
}

firebase.initializeApp(config)

export const db = firebase.firestore()

new Vue({
  router,
  store,
  render: function (h) { return h(App) }
}).$mount('#app')

Ok, so what have we just done? We’ve added import firebase from ‘firebase’ to line 5, so we can use everything Firebase has to offer. On line 9 we’ve defined all our keys that we’ll be using to access our Firestore, then finally we’re exporting a constant called DB, this is assigned to firebase.firestore(), allowing us to import our Firestore DB into any of our .vue files going forward.

4.2.2 Firestore

Hop back into the Firebase console and press Database on the left menu. We want to create a Cloud Firestore Database.

We’re going to start in test mode, for now, Firestore has completely customizable rules to grant and deny access to your data, but we don’t need to worry about that here, we’ll cover it at another time.

Adding a collection is easy!
Adding a collection is easy!

4.2.3 Inserting our data to Firestore

Before we insert our data, we want to make sure that we’re not putting in blank rows, so we’ll trap it and display an error. Simple right?

Open up src/views/Home.vue and we’re going to be modifying within the <script> tags of this file.

First, we define our errors ‘variable’ (line 17) within our data function and also import the DB constant (line 10) that we created in main.js, this will allow us to use the Firestore functions within this file.

<script>
import { db } from '@/main'

export default {
  name: 'home',
  data: function () {
    return {
      myTodo: '',
      errors: ''
    }
  },

We need to do a simple check that there is some data before we submit to Firestore, if not we’re going to ask the user to enter some text.

methods: {
    addToDo: function () {
      this.errors = ''

      if (this.myTodo !== '') {

      } else {
        this.errors = 'Please enter some text'
      }
    }
  }

 

The last step, let us add an error output into the template, insert this div within the <template> section of Home.vue, below the input and button.

<div v-if="errors !== ''" id="errors">{{ errors }}</div>

To explain briefly, the v-if on the div is checking that errors have some data in it, otherwise, the div is hidden by Vue. Try it out! You should see the error pop up if you submit an empty field.

 

Ok, so let’s begin adding data to our collection. Inside the addToDo method, we want to replace what’s there with the following code:

this.errors = ''
if (this.myTodo !== '') {
  db.collection('items').add({
    title: this.myTodo,
    created_at: Date.now()
  }).then((response) => {
    if (response) {
      this.myTodo = ''
    }
  }).catch((error) => {
    this.errors = error
  })
} else {
  this.errors = 'Please enter some text'
}

Your addToDo method should look something like the screenshot below:

Switch back over to your Firestore database and you should now be seeing data appearing.

Your Home.vue file should look like this now… Click to copy from Pastebin if you’re stuck or something isn’t quite working right.

4.2.4 Real-Time Data

Open up /src/store.js and you’ll see the default structure, it might not look like much but this file will power the real-time element of our application and allow you to create a single method to retrieve items across all of your Vue files.

4.2.4.1 Creating the store

Please Note: I’ve included a pastebin of the final file in case you get lost along the way, or want to see it in action and learn later… you can access that here. If you choose to do this, please skip to step 4.3

Ok, let us begin. We’re going be referring to everything as items, it matches our collection and it’s best to keep everything the same.

Firstly, we need to import our Firestore DB to the store. So add the import to the top of the file as I’ve done below on line 4, we also won’t be using isContext so we can remove this too.

import Vue from 'vue'
import Vuex from 'vuex'
import { db } from '@/main'
 
Vue.use(Vuex)

 

 

We now need to define what is in our store, we do this by adding it to the state object, we’re also going to create a new method called getters.

export default new Vuex.Store({
  state: {
    items: null
  },
  getters: {
    getItems: state => {
      return state.items
    }
  },

 

This is simply defining that in our current state we have a null value called items that can be used within our app, we can retrieve it using our getter. Now let’s add our action.

We’re going to create a new method in actions called setItems, this will be the action that gets called when we want to dispatch a call to retrieve our items from Firestore.

},
  actions: {
    setItems: context => {
      context.commit('setItems')
    }
  }

 

Now to create setItems within our mutations the object, this is the most important bit.

mutations: {
    setItems: state => {
      let items = []
 
      db.collection('items').orderBy('created_at').onSnapshot((snapshot) => {
        items = []
        snapshot.forEach((doc) => {
          items.push({ id: doc.id, title: doc.data().title })
        })
 
        state.items = items
      })
    }
  },

Let me explain what this is doing. Firstly we’re creating an empty array called items and resetting it when we’ve got data from Firebase, then pushing it to our store at the end. This is to create a smooth update to our store, you cannot simply append data to the store because later on when we’re deleting items, you’ll want them taking out too and we don’t want any old data sets hanging around on our front end, no sir!

onSnapshot magic!

We’re essentially assigning a listen event to our items collection in Firestore. Any updates to the documents, from anywhere, will then run the code within onSnapshot. This isn’t restricted to your local window if you were to deploy this code as an app or open it up in multiple tabs and windows on your PC or send your network link to another device— they would all update at the same time, completely in sync.

Your completed file should look like this, again the pastebin link is here if you don’t have it quite right.

4.3 Back to the Front End

Our data is ready to go. Let’s navigate back to /src/views/Home.vue and get it outputting.

We’re going to add a new block to our <script> tag called beforeCreate. Any code within this block will be fired as soon as Vue is initialized, so our data will be ready for use straight away. I’ve included the life cycle of a Vue document (Copied from the Vue.js documentation) to give you a visual guide of when it’s running.

Credit: Vue.js documentation

We need to tell our store that we want to load in our items, so we’ll do this using beforeCreate.

<script>
import { db } from '@/main'

export default {
  name: 'home',
  beforeCreate: function () {
    this.$store.dispatch('setItems')
  },
  data: function () {
    return {
      myTodo: '',
      errors: ''
    }
  },

 

We’ll now be able to access all our Firestore items by calling this.$store.getters.getItems which we created in our store not long ago! Let us go get it. We’re going to need to loop through the data and display the output, in Vue this is spectacularly easy. Put it below your errors div, like so:

<div v-if="this.$store.getters.getItems && this.$store.getters.getItems.length > 0">
      <div class="title">Today, you've go to do...</div>
 
     <div v-for="item in this.$store.getters.getItems" :key="item.id">
       {{ item.title }}<br /><br /><small style="text-decoration:underline;" @click="deleteItem(item.id)">Delete</small>
       <hr />
     </div>
   </div>

A very quick overview, we’re using v-if to check if there are any items, if not we’ll be hiding our looped div, then we’re looping through the items display the title of our item. Easy!

You’ll notice I’ve added another click event called deleteItem. Slightly self-explanatory, this is going to be so we can delete our items from the front end. We’ll need to add our method below before the page will work as you’ll be getting an undefined error cropping up.

4.3.1 Deleting Items

Add a method below addToDo, called deleteItem. We’ll need to pass our document ID through to it and then we can tell Firestore to delete the document.

deleteItem: function (id) {
      if (id) {
        db.collection("items").doc(id).delete().then(function() {
          console.log('Document successfully deleted')
        }).catch(function(error) {
          this.error = error
        })
      } else {
        this.error = 'Invalid ID'
      }
    }

You can get the full Home.vue file here.

Open up your browser and providing all our syntax is correct, you’ll be able to add your to-do items, view them, and delete them — in real time across any device.

4.4 Conclusion

I find the best way to test this and to really see how great what you’ve created is, is to tab back into your Powershell window and grab your network URL.

Local for your machine, network for another device
Local for your machine, network for another device

Providing your mobile device or tablet is on the same network as the one you’re running this tutorial on – open it up on any devices browser and watch them both seamlessly update, in real time. If you don’t have another device, you can use your localhost URL and open two tabs or different browsers next to each other and they will update each other in real time and never lose their sync.

Moving Forwards

I hope that you’ve found this tutorial helpful. If the interest in there, I would consider the next one to be how to package this up as an Android app and run it on mobile natively.

SHARE ON

The Author: Dan W

How To Create a Real Time To Do List App with Vue, Vuex & Firebase Tutorial

You May Also Like

Leave a Reply

Your email address will not be published.