In this tutorial, we are going to build a simple CRUD application using Vue, Node.js, and MongoDB.

But first, let’s get our system set up for Vue development. we will start by creating API for our app

Creating the API

Before getting started You have to install Mongo locally. To do this, please visit the official download page, you might also like to install Compass, the official GUI for MongoDB.

Let’s get started by the creating directories, files and initializing the project:

mkdir vurcrudappapi cd vurcrudappapi touch server.js

Create a folder called API

mkdir api

Inside this folder called API, create three separate folders called models, routes, and controllers by running:

mkdir api/controllers mkdir api/models mkdir api/routes

Create taskController.js in the api/controller folder , taskRoutes.js in the routes folder, and taskModel in the model folder

touch api/controllers/taskController.js touch api/model/taskModel.js touch api/routes/taskRoutes.js

now run the following command to create a package.json file

npm init

Install the dependencies

To install the dependencies for our API execute the following commands:

npm i express cors body-parser mongoose npm i nodemon --save-dev

Next, open up package.json and alter the scripts section to read as follows:

"scripts": { "start": "nodemon server.js" },

Setting up the Server

Add the following code to taskController.js :

const mongoose = require('mongoose'); const task = mongoose.model('task'); exports.list_all_tasks = (req, res) => { task.find({}, (err, tasks) => { if (err) res.send(err); res.json(tasks); }); }; exports.create_a_task = (req, res) => { const newTask = new task(req.body); newTask.save((err, task) => { if (err) res.send(err); res.json(task); }); }; exports.read_a_task = (req, res) => { task.findById(req.params.taskId, (err, task) => { if (err) res.send(err); res.json(task); }); }; exports.update_a_task = (req, res) => { task.findOneAndUpdate( { _id: req.params.taskId }, req.body, { new: true }, (err, task) => { if (err) res.send(err); res.json(task); } ); }; exports.delete_a_task = (req, res) => { task.deleteOne({ _id: req.params.taskId }, err => { if (err) res.send(err); res.json({ message: 'task successfully deleted', _id: req.params.taskId }); }); };

Add the following code to taskModel.js :

const mongoose = require('mongoose'); const { Schema } = mongoose; const taskSchema = new Schema( { task1: { type: String, required: 'task1 cannot be blank' }, task2: { type: String, required: 'task2 cannot be blank' } }, { collection: 'task' } ); module.exports = mongoose.model('task', taskSchema);

Similarly, Add the following code to RoutesModel.js :

const taskBuilder = require('../controllers/taskController'); module.exports = app => { app .route('/tasks') .get(taskBuilder.list_all_tasks) .post(taskBuilder.create_a_task); app .route('/tasks/:taskId') .get(taskBuilder.read_a_task) .put(taskBuilder.update_a_task) .delete(taskBuilder.delete_a_task); };

Finally, Open up server.js and add the following code:

const express = require('express'); const cors = require('cors'); const mongoose = require('mongoose'); const bodyParser = require('body-parser'); global.Task = require('./api/models/taskModel'); const routes = require('./api/routes/taskRoutes'); mongoose.Promise = global.Promise; mongoose.set('useFindAndModify', false); mongoose.connect( 'mongodb://localhost/Vuecrudapp', { useNewUrlParser: true } ); const port = process.env.PORT || 3000; const app = express(); app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); routes(app); app.listen(port); app.use((req, res) => { res.status(404).send({ url: `${req.originalUrl} not found` }); }); console.log(`Server started on port ${port}`);

In the code snippet above, we are using Mongoose’s connect method to connect to our database Vuecrupsapp which I have created using mongo Compass, before creating a new Express app and telling it to use the bodyParser and cors middleware.

We then tell it to use the routes we defined in api/routes/taskRoutes.js and to listen for connections on port 3000. Finally, we define a function to deal with nonexistent routes.

Now, start the node and MongoDB server by executing the following commands:

npm run start mongod

Testing the API

Start up PostMan App to test create the method of our API

We’ll start off by creating a new Tasks lists. Select POST as the method and enter http://localhost:3000/ tasks as the URL.

testing the API via postman

Now, enter two key pairs into the fields below and press Send. you will receive the newly created object from the server by way of a response.

Creating the Vue Crud App

Install Vue CLI

Vue CLI requires node.js version 8.9 and above. So ensure you have the right version of node.js installed. You can check your node.js version by entering this on your console.

node -v

If you don’t have node.js installed, you can install it by heading over to the node.js website.

After setting up node.js, install Vue CLI by running the command shown below in your console:

npm install -g @vue/cli

This command installs Vue CLI globally and it can now be accessed from the terminal. To confirm the Vue CLI installation, run:

vue --version

You should get an output stating the version of your Vue installation.



If the installation was successful, run the command below to create a new Vue CLI project.

vue create vuecrudapp

You will be prompted to pick a preset. For this app, the default preset is just fine. Select the default preset and let vue CLI scaffold our project.

To see what our app looks like, let’s serve it:

cd vuecrudapp npm run serve

Go to http://localhost:8080/ to view your Vue application.

Your application should look like this.

Let’s get started by creating directories and files :

cd src touch components/TaskTest.vue touch components/Taskform.vue mkdir views touch views/Edit.vue touch views/New.vue touch views/Show.vue touch views/Test.vue touch views/Tasks.vue mkdir helpers touch helpers/Helpers.js touch Router.js

Install Dependencies

For the front end of our Vue crud app, we’ll be using the Axios, Sematic-UI, and Vue flash libraries. you can install them by executing following command:

npm i axios semantic-ui-css vue-flash-message

Now, Open router.js file and add the following code to it:

import Vue from 'vue'; import Router from 'vue-router'; import Tasks from './views/Tasks.vue'; import New from './views/New.vue'; import Show from './views/Show.vue'; import Edit from './views/Edit.vue'; Vue.use(Router); export default new Router({ mode: 'history', base: process.env.BASE_URL, linkActiveClass: 'active', routes: [ { path: '/', redirect: '/tasks' }, { path: '/tasks', name: 'tasks', component: Tasks }, { path: '/tasks/new', name: 'new-task', component: New }, { path: '/tasks/:id', name: 'show', component: Show }, { path: '/tasks/:id/edit', name: 'edit', component: Edit } ] });

In the code snippet above, we have created the following Routes for our app

/tasks —display all the tasks in the database

—display all the tasks in the database /tasks/new —create a new task

—create a new task /tasks/:id —display a task

—display a task /tasks/:id/edit —edit a task

Now update the code of main.js with the following:

import Vue from 'vue' import App from './App.vue' import 'semantic-ui-css/semantic.css'; import router from './router' new Vue({ router, render: h => h(App), }).$mount('#app')

and Finally, update the content of the app.vue with the following code:

<template> <div id="app"> <div class="ui inverted segment navbar"> <div class="ui center aligned container"> <div class="ui large secondary inverted pointing menu compact"> <router-link to="/tasks" exact class="item"> <i class="tasks icon"></i> Tasks </router-link> <router-link to="/tasks/new" class="item"> <i class="plus circle icon"></i> New </router-link> </div> </div> </div> <div class="ui text container"> <div class="ui one column grid"> <div class="column"> <router-view /> </div> </div> </div> </div> </template> <script> export default { name: 'app' }; </script> <style> #app > div.navbar { margin-bottom: 1.5em; } .myFlash { width: 250px; margin: 10px; position: absolute; top: 50; right: 0; } input { width: 300px; } div.label { width: 120px; } div.input { margin-bottom: 10px; } button.ui.button { margin-top: 15px; display: block; } </style>

Update the code of components/TaskForm.Vue with the following:

<template> <form action="#" @submit.prevent="onSubmit"> <p v-if="errorsPresent" class="error">Please fill out both fields!</p> <div class="ui labeled input fluid"> <div class="ui label"> <i class="calendar plus icon"></i>task </div> <input type="text" placeholder="Enter task..." v-model="task.task1" /> </div> <div class="ui labeled input fluid"> <div class="ui label"> <i class="info circle icon"></i> Details </div> <input type="text" placeholder="Enter Details" v-model="task.task2" /> </div> <button class="positive ui button">Submit</button> </form> </template> <script> export default { name: 'task-form', props: { task: { type: Object, required: false, default: () => { return { task1: '', task2: '' }; } } }, data() { return { errorsPresent: false }; }, methods: { onSubmit: function() { if (this.task.task1 === '' || this.task.task2 === '') { this.errorsPresent = true; } else { this.$emit('createOrUpdate', this.task); } } } }; </script> <style scoped> .error { color: red; } </style>

Update the code of New.Vue with the following:

<template> <div> <h1>New task</h1> <task-form @createOrUpdate="createOrUpdate"></task-form> </div> </template> <script> import taskForm from '../components/TaskForm.vue'; import { api } from '../helpers/helpers'; export default { name: 'new-task', components: { 'task-form': taskForm }, methods: { createOrUpdate: async function(task) { const res = await api.createtask(task); this.flash('task created', 'success'); this.$router.push(`/tasks/${res._id}`); } } }; </script>

Update the code of Edit.Vue with the following:

<template> <div> <h1>Edit task</h1> <task-form @createOrUpdate="createOrUpdate" :task=this.task></task-form> </div> </template> <script> import taskForm from '../components/TaskForm.vue'; import { api } from '../helpers/helpers'; export default { name: 'edit', components: { 'task-form': taskForm }, data: function() { return { task: {} }; }, methods: { createOrUpdate: async function(task) { await api.updatetask(task); this.flash('task updated sucessfully!', 'success'); this.$router.push(`/tasks/${task._id}`); } }, async mounted() { this.task = await api.gettask(this.$route.params.id); } }; </script>

Update the code of Show.Vue with the following:

<template> <div> <h1>Show task</h1> <div class="ui labeled input fluid"> <div class="ui label"> <i class="tasks icon"></i> Task </div> <input type="text" readonly :value="task.task1"/> </div> <div class="ui labeled input fluid"> <div class="ui label"> <i class="info circle icon"></i> Details </div> <input type="text" readonly :value="task.task2"/> </div> <div class="actions"> <router-link :to="{ name: 'edit', params: { id: this.$route.params.id }}"> Edit task </router-link> </div> </div> </template> <script> import { api } from '../helpers/helpers'; export default { name: 'show', data() { return { task: '' }; }, async mounted() { this.task = await api.gettask(this.$route.params.id); } }; </script> <style scoped> .actions a { display: block; text-decoration: underline; margin: 20px 10px; } </style>

Update the code of Tasks.Vue with the following:

<template> <div> <h1>tasks</h1> <table id="tasks" class="ui celled compact table"> <thead> <tr> <th> <i class="calendar plus icon"></i>Task</th> <th> <i class="info circle icon"></i>Detail</th> <th> <i class="lock open icon"></i></th> <th> <i class="edit icon"></i></th> <th> <i class="trash icon"></i></th> <th colspan="3"></th> </tr> </thead> <tr v-for="(task, i) in tasks" :key="i"> <td>{{ task.task1 }}</td> <td>{{ task.task2 }}</td> <td width="75" class="center aligned"> <router-link :to="{ name: 'show', params: { id: task._id }}">Show</router-link> </td> <td width="75" class="center aligned"> <router-link :to="{ name: 'edit', params: { id: task._id }}">Edit</router-link> </td> <td width="75" class="center aligned" @click.prevent="onDestroy(task._id)"> <a :href="`/tasks/${task._id}`">Delete</a> </td> </tr> </table> </div> </template> <script> import { api } from '../helpers/helpers'; export default { name: 'tasks', data() { return { tasks: [] }; }, methods: { async onDestroy(id) { const sure = window.confirm('Are you sure?'); if (!sure) return; await api.deletetask(id); this.flash('task deleted sucessfully!', 'success'); const newtasks = this.tasks.filter(task => task._id !== id); this.tasks = newtasks; } }, async mounted() { this.tasks = await api.gettasks(); } }; </script>

In the code snippet above we are importing our API to perform operations against the back end, then in the component’s mounted lifecycle hook we’re calling its gettasks method to fetch all of the words from the database.

Communicating with the API

Add the following code to helpers/helpers.js

import axios from 'axios'; import Vue from 'vue'; import VueFlashMessage from 'vue-flash-message'; import 'vue-flash-message/dist/vue-flash-message.min.css'; Vue.use(VueFlashMessage, { messageOptions: { timeout: 3000, pauseOnInteract: true } }); const vm = new Vue(); const baseURL = 'http://localhost:3000/tasks/'; const handleError = fn => (...params) => fn(...params).catch(error => { vm.flash(`${error.response.status}: ${error.response.statusText}`, 'error'); }); export const api = { gettask: handleError(async id => { const res = await axios.get(baseURL + id); return res.data; }), gettasks: handleError(async () => { const res = await axios.get(baseURL); return res.data; }), deletetask: handleError(async id => { const res = await axios.delete(baseURL + id); return res.data; }), createtask: handleError(async payload => { const res = await axios.post(baseURL, payload); return res.data; }), updatetask: handleError(async payload => { const res = await axios.put(baseURL + payload._id, payload); return res.data; }) };

In the code snippet above we’re exporting an api object that exposes methods corresponding to the endpoints. These methods will make Ajax calls to our back end, which will carry out the various Vue CRUD operations.

Here is the Final Result:

VUe Crud app – CodeSource.io

Conclusion

I hope you learned a few things about Vue. Every article can be made better, so please leave your suggestions and contributions in the comments below. If you have questions about any of the steps, please do ask also in the comments section below.

You can access CodeSource from here.

