Vuefire CRUD Todo List App — Part 1

Use Vue.js, Firebase’s Cloud Firestore, and Vuefire to create a full-stack app

Nathan Magyar
8 min readMar 17, 2019

Part 1 (this article)

Part 2

This is the first of a 2-part series on making a full-stack application with Vue.js, Firebase’s Cloud Firestore, and Vuefire. We’ll be going through the basic requirements for a todo list CRUD application (create, read, update, delete). By the end you’ll have an app with persistent data storage, as well as a better understanding of Vue and Firebase technologies.

I won’t be going into as much detail on the basics of Vue in this series, so if you’re newer to it I recommend checking out my other Online Store tutorial series first!

Step 1: Project setup

We’ll be using the Vue CLI to get our project started, which requires Node.js version 8.9 or above (8.11.0+ recommended). Check then you have Node by running the following command in your terminal:

node -v

If you get something greater than or equal to v8.11.0 then you’re good to go. If not, click the link above to download and install the appropriate version for your machine.

Next, install the Vue CLI with:

npm install vue-cli -g

Create a new project named v-fire with the webpack simple template using

vue init webpack-simple v-fire

Complete the project configuration steps by setting the project name, description, author, and sass settings:

? Project name v-fire
? Project description A CRUD application using Vue and Firebase
? Author Nathan Magyar <magyarn@umich.edu>
? License MIT
? Use sass? Y

The project was successfully created if you receive:

    vue-cli · Generated "v-fire".    To get started:
cd v-fire
npm install
npm run dev

cd into the v-fire directory and install the necessary dependencies that Vue CLI automatically declares for us:

cd v-fire
npm install

In the same directory, let’s now install Firebase and VueFire through npm. Firebase will serve as the database for our project, and VueFire gives us an easy way to communicate between that database and our Vue app:

npm install firebase vuefire

Start the development server to make sure your project has been successfully created, then visit localhost:8080/ in your browser:

npm run dev

In your code editor, open App.vue, which is located in v-fire/src. Delete all of the contents in the template, except for the outermost div.

<template>
<div id="app">
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<style>
...
</style>

Step 1.2: Set up Firebase

In your browser, go to Firebase and log in with your Google account (create a Google account if necessary). Click the “Go to Console” button.

Click “Add project”, enter a project name, accept the terms & conditions, then click “Create project”. Once your project is ready, click “Continue” to go to the project dashboard.

Access the necessary Firebase project keys. We’re making a web app, so we’ll click the </> icon on the project dashboard page:

Copy everything inside of the object stored in config:

apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
databaseURL: "YOUR_DATABASE_URL",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGE_SENDER_ID"

We use the above contents to connect our application to Firebase. To make this happen, create a new file under src called firebase.js and add the following import statement and constant:

// firebase.jsimport { initializeApp } from 'firebase';const app = initializeApp({
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
databaseURL: "YOUR_DATABASE_URL",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGE_SENDER_ID"
});

Our app doesn’t actually need all of the above configuration options, so in production we might trim them down to only include the ones that are necessary. But let’s not worry about that now.

To give ourselves easier access to our database (db) and our todos (todosCollection) in the future, let’s add a few exported constants below the initializeApp function:

export const db = app.firestore();
export const todosCollection = db.collection('todos');

In Firebase, our data is stored in the following way. At the top level are “collections” which we can specify names for, like todos. Inside each collections are “documents”, which are individual todo items for us. We can choose to name these documents as well, or let Firebase generate a random id and set that as the name. Inside every document are the fields we specify, like a todo item’s text or completed status. Fun fact: not all documents inside the same collection have to have the same fields.

Next, make sure our Firebase app gets initialized by importing the above file in main.js:

import './firebase'import Vue from 'vue'
import App from './App.vue'
import VueFire from 'vuefire'
Vue.use(VueFire);new Vue({
el: '#app',
render: h => h(App)
})

Above we also import the VueFire plugin and install it with the Vue.use() method.

Step 2: “Add new todo” functionality

In App.vue, update the template to include a form element that consists of a text input field, wrapped in a label:

Above we also use the v-model directive to connect the input field to our component, and the prevent modifier on the button’s click handler to stop the page from reloading when the user adds a new todo. You’ll also notice the addTodo() method that we’ll soon write to handle adding the todo item to our database.

Now write the addTodo() method in a methods section in App.js:

We start by importing our todosCollection exported constant from firebase.js at the top of the script section. Inside addTodo(), we use the add method to add a new todo object onto this collection. The new todo consists of text, completed, and createdAt properties. The text property is where we add our newTodo data property. All new todos will be incomplete by default, so completed is set to false. For createdAt we use a new instance of Javascript’s Date object to record the time the todo was submitted. As a start to handling any errors, we add then and catch clauses. If the addition was successful, we’ll get a message in our console saying Document written with ID: blahBlah1234. If not, we’ll get the other message. Finally, after all that, reset the newTodo data property to an empty string so the last todo’s text doesn’t persist after being submitted.

Next, make sure you have a database you can write to and read from. Go to the Firebase project’s database dashboard and click “Create new database”, opt into “test mode” for development purposes (that was we don’t have to deal with authentication just yet), and hit “Create”.

Note: Test mode should only be used for development purposes. Production apps should not have read and write rules that are so flexible.

Now if you test this out in your browser, then go over to the database view in Firebase (you may have to refresh the page), you’ll see the new todos collection, a new document with some random key/name, and then our new todo content in the far right column. Sweet!

Next let’s get our todos displaying on the page. To query our Firestore database, we’ll use the firestore() method, which returns a data object. Inside that object is a key equal to the name of our collection, in this case todos. It will return the todosCollection constant we imported at the top of the script section. Our queries will be ordered by most recently added to oldest, using the orderBy method, which receives the createdAt string and the desc option (for “descending”).

Then we must add a todos data property in the component itself, which Firestore will populate with the data. And finally, in the addTodo() method add one new key, id, to help us when we loop over the todos in the future. Each id will be set to the length of this.todos for now.

To make the todos appear, update the template to include a ul and an li for each todo, which renders each todo's text:

Remove the “display: inline-block” style line from the “li” to make list items render vertically again.

Step 3: Checking off todo items

Now let’s add functionality to allow users to check off a todo item. Start by updating the template. Inside the li for each item, add a label element that wraps a checkbox input and the todo item’s text:

...
<ul>
<li v-for="todo in todos" :key="todo.id">
<label>
<input
type="checkbox">
{{todo.text}}
</label>
</li>
</ul>
...

On the checkbox input, add two more attributes, v-model and @change. v-model will connect the checkbox to a given todo item’s completed attribute, and @change will trigger a method called updateTodo() whenever a user checks or unchecks the item (provided we pass in the todo as an argument):

...
<ul>
<li v-for="todo in todos" :key="todo.id">
<label>
<input
type="checkbox"
v-model="todo.completed"
@change="updateTodo(todo)">
{{todo.text}}
</label>
</li>
</ul>
...

Now write the updateTodo() method. It will work similarly to addTodo().

updateTodo(todo) {
todosCollection.doc(todo.id).update({...todo})
.then(function(docRef) {
console.log("Updated document with ID: ", todo.id);
})
.catch(function(error) {
console.error("Error updating document: ", error);
});
}

Even though we didn’t add/create it explicitly, each todo document is given an id by Firestore, which can be accessed by writing todo.id. This is how we tell Firestore which document to update. On todosCollection, we call the doc() method and pass in this id. Firestore then locates the document we want and allows us to call update(), the method we use to change part of an existing record. While we could just pass in just the key and value for the todo's completed property, I’m using the spread operator ( ... ) to pass in all of the contents because in the future we could use this same method to update the todo's text. The rest of the method does the same sort of success confirmation/error messaging in the console.

Now when you check a checkbox input on your screen and then refresh the database page in Firebase you should see the corresponding todo item’s completed value change appropriately.

Step 4: Make it pretty

Finally, add some classes to template elements and corresponding CSS rules to make the page a bit nicer:

Next steps

We can now create, read, and (sort of) update todo items. In the second part we’ll work on being able to update the text and delete todo items. 👋

--

--

Nathan Magyar

User Experience Designer and Front End Developer, University of Michigan Office of Academic Innovation