Build a Todo App with Node.Js, ExpressJs, MongoDB and VueJs – Part 2

In part 1 of this tutorial, we built APIs for a simple todo application and now we are here to put the front end together with VueJS. You need not to worry if you are new to VueJs. I wrote VueJs: The basics in 4 mins and Creating your first component in VueJs to help you pick up VueJs in no time.

Packages

There is a need to bundle JS and CSS resources into one javascript bundle that will be included in index page and we’ll choose webpack for this purpose. We’ll use a single package.json to manage dependencies for both backend and frontend.

Having said that, let’s go ahead and update package.json.

{
    "name": "node-todo",
    "version": "0.0.1",
    "description": "Simple todo application.",
    "main": "server.js",
    "author": "Samuel James",
    "scripts": {
        "build": "webpack"
    },
    "dependencies": {
        "axios": "^0.16.2",
        "body-parser": "^1.5.2",
        "bootstrap": "^4.0.0-beta.2",
        "express": "~4.7.2",
        "method-override": "~2.1.2",
        "mongoose": "~4.0.0",
        "morgan": "^1.9.0",
        "vue": "^2.5.11",
        "vue-axios": "^2.0.2"

    },
    "devDependencies": {
        "prettier": "^1.9.2",
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-preset-env": "^1.6.0",
        "babel-preset-stage-3": "^6.24.1",
        "cross-env": "^5.0.5",
        "css-loader": "^0.28.7",
        "file-loader": "^1.1.4",
        "vue-loader": "^13.0.5",
        "vue-template-compiler": "^2.4.4",
        "webpack": "^3.6.0",
        "webpack-dev-server": "^2.9.1"
    }
}

From console, run npm install to install dependencies.

Create a ‘src’ folder in public folder. We’ll put all our source code for frontend in this src folder.

Configuring webpack

We required webpack in our package.json file if you can still remember, It’s time to configure how our it should be build our assets. Create a webpack.config.jsat the root folder of the project.

module.exports = {
  entry: './public/src/main.js',
  output: {
    filename: './public/build/bundle.js'
  },
  resolve: {
    alias: {
      vue: './vue.js'
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }, {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
          }
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  }
}

While I would not go into much details about webpack configuration in this post, you can head on to webpack documentation for a hands on webpack.

In webpack.config.js, we defined a vue alias. This is because the runtime build is used by default when you import vue from 'vue' in your project. The thing is template compiler is not available in the runtime build and hence the reason why we defined an alias in webpack.config.js. Download a standalone version of Vue into public folder (todo-app/public/src/vue.js). Webpack will build our assets using the standalone version.

Creating the Vue Components

Basically, we need 2 major vue components: one for creating new items and the other for listing, updating and deleting todo items. At some points, these components would need to communicate or share data with each other and this where event bus comes into play.

One of the best ways to handle communications between components in Vue.Js is to use a global event bus such that when a component emits an event, an event bus transmits this event to other listening components.

Event Bus

We create a global event bus with this code :

//path/to/project/public/src/bus.js
'use strict'

import Vue from 'vue'
const bus = new Vue()

export default bus

Create Todo Component

Now that we have event bus created, let write the code for adding new todo items.

//todo-app/public/src/components/CreateTodo.vue
<template>
  <div>
    <h2>Create a Todo List</h2>
    <form @submit.prevent>
      <div class="form-group">
        <input type="text" class="form-control" @keypress="typing=true" placeholder="What do you want to do?" v-model="todo" @keyup.enter="addTodo($event)">
        <span class="help-block small text-center" v-show="typing">Hit enter to save</span>
      </div>
    </form>
  </div>
</template>

<script>
  import axios from 'axios';  
  import bus from "./../bus.js";
  
  export default {
    data() {
      return {
        todo: '',
        typing: false,
      }
    },
    methods: {
      addTodo(event) {
        if (event) event.preventDefault();
        let url = 'http://localhost:4000/api/add';
        let param = {
          name: this.todo,
          done: 0
      };
        axios.post(url, param).then((response) => {
          console.log(response);
          this.clearTodo();
          this.refreshTodo();
          this.typing = false;
        }).catch((error) => {
          console.log(error);
        })
      },
      clearTodo() {
        this.todo = '';
      },
      refreshTodo() {
        bus.$emit("refreshTodo");
      }
    }
  }
</script>

That out of the way, we also need to write code to get and also update todo items in the database.

List Todo Component

Create a file ListTodo.vue in components’ folder:

//todo-app/public/src/components/ListTodo.vue

<template>
    <div>
        <div class="col-md-12" v-show="todos.length>0">
            <h3>Todo Items</h3>
            <div class="row mrb-10" v-for="todo in todos">
                <div class="input-group m-b-5">
                    <span class="input-group-addon addon-right">
                        <input type="checkbox" v-model="todo.done"
                               :checked="todo.done" :value="todo.done"
                               v-on:change="updateTodo(todo)"
                               title="Mark as done?"/></span>

                    <input type="text" class="form-control" :class="todo.done?'todo__done':''" v-model="todo.name"
                           @keypress="todo.editing=true" @keyup.enter="updateTodo(todo)">
                    <span class="input-group-addon addon-left" title="Delete todo?"
                          v-on:click="deleteTodo(todo._id)">X</span>
                </div>
                <span class="help-block small" v-show="todo.editing">Hit enter to update</span>
            </div>
        </div>
        
        <div class="row alert alert-info text-center" v-show="todos.length==0">
            <p class="alert alert-info">
                <strong>All Caught Up</strong>
                <br/>
                You do not have any todo items</p>
        </div>
    </div>
</template>

<script>
    import axios from 'axios';
    import bus from './../bus.js'

    export default {
        data() {
            return {
                todos: []
            }
        },
        created: function () {
            this.fetchTodo();
            this.listenToEvents();
        },
        methods: {
            fetchTodo() {
                let uri = 'http://localhost:4000/api/all';
                axios.get(uri).then((response) => {
                    this.todos = response.data;
                });
            },
            updateTodo(todo) {
                let id = todo._id;
                let uri = 'http://localhost:4000/api/update/' + id;
                todo.editing = false;
                axios.post(uri, todo).then((response) => {
                    console.log(response);
                }).catch((error) => {
                    console.log(error);
                })
            },

            deleteTodo(id) {
                let uri = 'http://localhost:4000/api/delete/' + id;
                axios.get(uri);
                this.fetchTodo();
            },

            listenToEvents() {
                bus.$on('refreshTodo', ($event) => {
                    this.fetchTodo(); //update todo
                })
            }
        }
    }
</script>

<style scoped>
    .todo__done {
        text-decoration: line-through !important
    }
    .no_border_left_right {
        border-left: 0px;
        border-right: 0px;
    }
    .flat_form {
        border-radius: 0px;
    }
    .mrb-10 {
        margin-bottom: 10px;
    }
    .addon-left {
        background-color: none !important;
        border-left: 0px !important;
        cursor: pointer !important;
    }
    .addon-right {
        background-color: none !important;
        border-right: 0px !important;
    }
</style>

The code is pretty straight forward but I will do justice by taking a moment to explain what is going on here.

We created 4 functions in the snippet : ` fetchTodo() makes calls to backend and fetches todo items , updateTodo(param) is called when changes are made to todo items. Basically, updateTodo()` forwards your changes to backend where it can be persisted.

When you click on delete (X button), deleteTodo(param) is executed and thus remove the item. When a todo item is deleted successfully from the database, it’s idea to update our page to reflect the change. Since we have multiple components, the only way to let component B know that todo item A is no longer available is to fire an event (refreshTodo) and the listening component B can now request for a fresh list. That said, fetchTodo() is executed when ever refreshTodo event is fired and our page is updated.

App Component

Below we wrap all our components in a parent component named App.

//todo-app/public/src/components/App.vue

<template>
  <div id="app">
    <div class="container">
      <div class="row col-md-12 d-flex justify-content-center">
        <create-todo></create-todo>
        <div class="row col-md-12 d-flex justify-content-center">
        <list-todo></list-todo>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import CreateTodo from './CreateTodo.vue';
  import ListTodo from './ListTodo.vue';
  
  export default {
    name: 'app',
    components: {CreateTodo, ListTodo},
  }
</script>

<style>
  .fade-enter-active,
  .fade-leave-active {
    transition: opacity .5s
  }
  .fade-enter,
  .fade-leave-active {
    opacity: 0
  }
</style>

Root Instance

A root instance must be defined for every vue application. We can see a Vue instance or root instance as the root of the tree of components that make up our app. We define a root instance by creating a new file main.js in public folder with this code.

//todo-app/public/main.js
'use strict'
import Vue from 'vue'
import 'bootstrap/dist/css/bootstrap.min.css';
import App from './components/App.vue'

new Vue({
  el: 'app',
  components: {App},
  methods: {}
})

Let’s take a moment to go through what is happening in the file we just created. We imported boostrap css and also imported App from App Component and defined as a component on the root instance. Pretty simple, yeah?

We’ve come this far, we can now build our assets by running:

$ npm run build

You should now see a new file named bundle.js created in /public/build folder. To wrap this up, we update our index page created in part 1 of this tutorial with this code:

<!--todo-app/public/index.html-->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>VueJS NodeJS and Express  Todo App</title>
  </head>
  <body>
    <app></app>
    <script src="/build/bundle.js"></script>
  </body>
</html>

If you start your server and navigate to http://localhost:4000, you will be greeted with a page like this. This source code of this application can be found here