Vuefire CRUD Todo List App — Part 1
Use Vue.js, Firebase’s Cloud Firestore, and Vuefire to create a full-stack app
Part 1 (this article)
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
:
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. 👋