loading...

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

abiodunjames profile image Samuel James Updated on ・6 min read

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. Dont 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.

Project Directories

In part 1, we created backend directory. The backend directory contains the source code for our backend code.

We'll do something simillar here. Let's create a new directory with the name frontend. This will house our frontend code.

$ mkdir frontend

If you run the command above, your project directory should now look like this:

.
├── backend
└── frontend

All our code in this post will go in to frontend directory.

Vue CLI

Vue CLI is a command line tool that helps you quickly scaffold a new project. To install Vue CLI, run the command below from your terminal:

$ npm install -g @vue/cli

With the Vue Cli installed, go to frontend directory run vue create . from the command to scaffold a new project.

$ vue create .

Ensure you answer yes to all prompts.

Vue CLI v3.5.1
? Generate project in current directory? Yes

? Please pick a preset: default (babel, eslint)

If everything went fine, your frontend directory will look this:

├── README.md
├── babel.config.js
├── node_modules
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
└── src
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   └── HelloWorld.vue
    └── main.js


Project Dependencies

  1. Bootstrap Vue : A Vue compatible boostrap framework
  2. Sass loader: Compiles sass to css
  3. Axios : For making rest API calls to todo API

Install bootstrap-vue and axis with the command:

$ npm install vue bootstrap-vue bootstrap axios

Install sass-loader with the command:

$ npm install sass-loader node-sass --save-dev

In the following paragraph, we'll create components we need for this project.

Creating the Vue Components

Basically, we need 2 major vue components. The first component will be CreateTodo and the second will be ListTodo

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 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 the name src/bus.js with the code:

//src/bus.js

import Vue from 'vue';

const bus = new Vue();
export default bus;

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

Vue Component for adding new todo items

Create a new file at src/components/CreateTodo.vue and update its content with:


<template>
  <div class="col align-self-center">
    <h3 class="pb-5 text-left underline">Create todos</h3>
    <form class="sign-in" @submit.prevent>
      <div class="form-group todo__row">
        <input
          type="text"
          class="form-control"
          @keypress="typing=true"
          placeholder="What do you want to do?"
          v-model="name"
          @keyup.enter="addTodo($event)"
        />
        <small class="form-text text-muted" v-show="typing">Hit enter to save</small>
      </div>
    </form>
  </div>
</template>
<script>
import axios from "axios";
import bus from "./../bus.js";

export default {
  data() {
    return {
      name: "",
      typing: false
    };
  },
  methods: {
    addTodo(event) {
      if (event) event.preventDefault();
      let todo = {
        name: this.name,
        done: false //false by default
      };
      console.log(todo);
      this.$http
        .post("/", todo)
        .then(response => {
          this.clearTodo();
          this.refreshTodo();
          this.typing = false;
        })
        .catch(error => {
          console.log(error);
        });
    },

    clearTodo() {
      this.name = "";
    },

    refreshTodo() {
      bus.$emit("refreshTodo");
    }
  }
};
</script>
<style lang="scss" scoped>
.underline {
  text-decoration: underline;
}
</style>

  • addTodo() is executed once an enter key is pressed. It makes a POST request to the backend with the new todo item.
  • clearTodo() clears the input box once the todo item is saved.
  • refreshTodo() emits an event refreshTodo. This is useful when you add a new todo item. It makes sense to re-render the list so that the new item is displayed.

That explained, let's go ahead to create ListTodo component.

Component for listing todo items

Create a file src/components/ListTodo.vue with the code:

<template>
  <div v-bind:show="todos.length>0" class="col align-self-center">
    <div class="form-row align-items-center" v-for="todo in todos">
      <div class="col-auto my-1">
        <div class="input-group mb-3 todo__row">
          <div class="input-group-prepend">
            <span class="input-group-text">
              <input
                type="checkbox"
                v-model="todo.done"
                :checked="todo.done"
                :value="todo.done"
                v-on:change="updateTodo(todo)"
                title="Mark as done?"
              />
            </span>
          </div>
          <input
            type="text"
            class="form-control"
            :class="todo.done?'todo__done':''"
            v-model="todo.name"
            @keypress="todo.editing=true"
            @keyup.enter="updateTodo(todo)"
          />
          <div class="input-group-append">
            <div class="input-group-text">
              <span
                class="input-group-addon addon-left"
                title="Delete todo?"
                v-on:click="deleteTodo(todo._id)"
              >
                X
              </span>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div
      class="alert alert-primary todo__row"
      v-show="todos.length==0 && doneLoading"
    >Hardest worker in the room. No more todos now you can rest. ;)</div>
  </div>
</template>

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

export default {
  data() {
    return {
      todos: [],
      doneLoading: false
    };
  },
  created: function() {
    this.fetchTodo();
    this.listenToEvents();
  },
  watch: {
    $route: function() {
      let self = this;
      self.doneLoading = false;
      self.fetchData().then(function() {
        self.doneLoading = true;
      });
    }
  },
  methods: {
    fetchTodo() {
      this.$http.get("/").then(response => {
        this.todos = response.data;
      });
    },

    updateTodo(todo) {
      let id = todo._id;
      this.$http
        .put(`/${id}`, todo)
        .then(response => {
          console.log(response);
        })
        .catch(error => {
          console.log(error);
        });
    },

    deleteTodo(id) {
      this.$http.delete(`/${id}`).then(response => {
        this.fetchTodo();
      });
    },

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

<style lang="scss" 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>

Let's take a moment to explain what is going on in the code.

We created 4 functions in the snippet.

  • fetchTodo() makes a GET call to backend and gets all todo items.
  • updateTodo(todo) is called when you make changes to todo items and hit enter. It forwards your changes to the backend.
  • deleteTodo(id) runs when you click the trash button. It makes DELETE requests to the backend.
  • listenToEvents(): In CreateTodo component, we emit events when a new todo item is added so the list. ListTodo is responsible for rendering todo items. This method does the job of listening for refreshTodo event.

App Component

Below we wrap all our components in a parent component named App.vue. Update the file src/App.vue with this content:

<template>
  <div class="container">
    <div class="row vertical-centre justify-content-center mt-50">
      <div class="col-md-6 mx-auto">
        <CreateTodo></CreateTodo>
        <ListTodo></ListTodo>
      </div>
    </div>
  </div>
</template>

<script>
import CreateTodo from "./components/CreateTodo.vue";
import ListTodo from "./components/ListTodo.vue";

export default {
  name: "app",
  components: { CreateTodo, ListTodo }
};
</script>

<style lang="scss">
@import "node_modules/bootstrap/scss/bootstrap";
@import "node_modules/bootstrap-vue/src/index.scss";
.vertical-centre {
  min-height: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
}
.todo__row {
  width: 400px;
}
</style>

Root Instance

A root instance must be defined for every vue application. You can see a Vue instance or root instance as the root of the tree of components that make up our app.

Let's modify the content of src/main.js file with:

import Vue from 'vue';
import BootstrapVue from 'bootstrap-vue';
import axios from 'axios';
import App from './App.vue';

const http = axios.create({
  baseURL: process.env.BACKEND_URL ? process.env.BACKEND_URL : 'http://localhost/todos',
});

Vue.prototype.$http = http;

Vue.use(BootstrapVue);

Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App),
}).$mount('#app');

We imported BoostrapVue and other libraries needed by the application.
We also imported App component and defined it as a component on the root instance.

We imported axios, an http client and we configured the base url of the backend application. You should ensure the baseUrl matches your backend URL.

We've come this far, run the application with:

$ npm run serve

It could take a few moment to build. At the end, you should have a URL printend in the console:

App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.178.20:8080/
  Note that the development build is not optimized.
  To create a production build, run npm run build.

If you navigate to http://localhost:8080, you should be greeted with a page like this.
Todo App

To connect the frontend app with the backend, you also need to start the backend server.

Navigate to backend directory and run

$ npm start

Note:

  1. Your MongoDB connection URL must be properly configure in backend/config/Config.js and the MongoDB must be running.
  2. Your backend server must be running.
  3. Your frontend server must be running.

If you navigate to http://localhost:8080, you will be greeted with a page like this.

Get the source code here

Posted on May 5 '18 by:

abiodunjames profile

Samuel James

@abiodunjames

I build apps that change people's lives and help developers grasp new concepts.

Discussion

markdown guide
 

Hi,
I was following your tutorial line by line, and just before the last step( npm run build), I got an error "No configuration file found and no output filename configured via CLI option. A configuration file could be named 'webpack.config.js' in the current directory."
Please help.

 

I was able to get this up and running by doing a few things:

  1. make sure your public folder file structure matches what is in Samuels github repo here: github.com/abiodunjames/NodeJs-Tod...

Mine was off a little bit based on how I read the instructions. Make sure you check out the subfolders for consistency as well.

  1. Make sure you have a webpack.config.js in your root folder that looks like this: github.com/markjohnson303/vue-todo....

  2. Don't forget the last step of updating index.html to reference bundle.js

I had to make a couple changes to the webpack.config.js that Samuel has in his repo, but this should get you going.

 

Thanks Mark J. I got error when running 'npm run build'. So I followed your suggestions, checked for file structure and used your webpack.config.js.

I got passed this error. However, when I load the app, add/delete/all work but update function didn't work (.save callback function didn't fire).

So I made this change and it's now working:

todo.save({
function (error, todo) {
...
}
})

Changed to (removed {})

todo.save(
function (error, todo) {
...
}
)

Based on suggestions on best practice, I also changed file names to lowercase (todo.js, routes.js, config.js) and variable Todo -> todo.

 

Hi,
Sorry for late my response. I think you are webpack.config.js. github.com/abiodunjames/NodeJs-Tod...

 

Hi Samuel,
while running build i get: "ERROR in ./public/src/main.js
Module not found: Error: Can't resolve 'vue'"

I looked in the github and saw there is a Vue.js file. how did you add this to the project or did the build take care of this for you?

I have web pack issues too, but ended up manually creating webpack.cofig.js and c+p from git, but then got above error.

thanks again for content

 

Hi AshGale,

We could import Vue using NPM without defining an alias in webpack.config.js. The problem is NPM provides runtime only build where template compiler is not available. One could fix this by downloading an official Vue package and defining an alias in webpack.config.js. Check this github.com/vuejs-templates/webpack...

 

I followed the steps, and it doesn't work. I think it's better to edit the article with necessary additional steps.

Things that don't work for me:

  1. The code uses Webpack, but there's no webpack.config.js. When calling npm run build, there will be an error about the Webpack config not being found. I had to follow the steps described in Mark Johnson's comment to get the Webpack to work.

  2. Tutorial part 1 uses Morgan in server.js, but in this part 2, Morgan is removed from the dependencies. As a result, running node server will have an error. I realized that Morgan is included in the source code, though. So it seems that the code in the article is not exactly the same with the source code?

 

Thanks for this article Sam, I think it will be very useful to me in learning Vue etc. I haven't started studying the code yet, but can you or someone else advise me whether this is an isomorphic / SSR / universal app? TIA Brianoh.

 

Hello Samuel,

Let me say it is a great article. Application is working. Except update functionality seems not working for me.

I have checked browser console. Update request remains in pending status.

Can you help me out here?

 

I have enough programmers who write courses, although they can not do it and should never do it. The first part was quite understandable. But the second is a total failure. It's like a horse drawing course. Part one: draw circles where the head and torso will be. Part two: Draw the final details. It only saves you that you gave the sources...

 

I noticed that when updating the @keyup.enter is not working and the input remains in focus instead of returning to a blur state. I've tried some things but nothing has worked.is there a way to remove the focus when hitting the 'enter' button?

 

Hi Samuel, was this tutorial updated per comments below?

 

The tutorial and the source code were updated a few months ago. You can check the source code on github to be sure.

 

Hi,

Great tutorial. Thanks.

Only a doubt, it isn't finished, right?

 

Thank you. Yes, it's finished. Part 2 is the last part of the series or do you have any topics you would want me to write on?

 

Hi am getting a blank webpage when I run " node server.js " but if I check my terminal it shows " GET /build/bundle.js 404 0.345 ms - 154 " each time I refresh the page