Create a crud Application using Vue, Node and MongoDB
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.
Before we kick off
Learn Vue.js and modern, cutting-edge front-end technologies from core-team members and industry experts with our premium tutorials and video courses on VueSchool.io.
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.
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.
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/tasks/new
—create a new task/tasks/:id
—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:
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.