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
- Bootstrap Vue : A Vue compatible boostrap framework
- Sass loader: Compiles sass to css
- 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 anenter
key is pressed. It makes aPOST
request to the backend with the new todo item. -
clearTodo()
clears the input box once the todo item is saved. -
refreshTodo()
emits an eventrefreshTodo
. 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 aGET
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 makesDELETE
requests to the backend. -
listenToEvents()
: InCreateTodo
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 forrefreshTodo
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.
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:
- Your MongoDB connection URL must be properly configure in
backend/config/Config.js
and the MongoDB must be running. - Your backend server must be running.
- Your frontend server must be running.
If you navigate to http://localhost:8080, you will be greeted with a page like this.
Top comments (16)
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:
Mine was off a little bit based on how I read the instructions. Make sure you check out the subfolders for consistency as well.
Make sure you have a webpack.config.js in your root folder that looks like this: github.com/markjohnson303/vue-todo....
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...
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:
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.Tutorial part 1 uses Morgan in
server.js
, but in this part 2, Morgan is removed from the dependencies. As a result, runningnode 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?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 inwebpack.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...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 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?
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...
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
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?