Node.js + Vue.js + Mongo DB - CRUD example - MEVN stack development example

Hello everyone, today we will learn how to build a full-stack application that is a basic User Management Application using Node.js, MongoDB, and Vue.js.GitHub repository link is provided at the end of this tutorial. You can download the source code.

  • Node.js Express exports REST APIs & interacts with MongoDB Database utilizing Mongoose ODM.
  • Vue.js is an open-source, progressive JavaScript framework for building utilizer interfaces. Vue fortifies the component-based approach to building web apps – it includes single-file components that are independent and loosely coupled to enable better code reuse and more expeditious development

After completing this tutorial what we will build? 

We will build a full-stack web application that is a basic User Management Application with CRUD features: 
  • Create User 
  • List User
  • Update User
  • Delete User 

Following is the screenshot of our application -

-Retrieve all Users:


-Add a User:


-Update User:


We divided this tutorial into two parts. 

PART 1 - Rest APIs Development using Node.js and Express
PART 2 - UI development using Vue.js

PART 1 - Rest APIs Development using Node.js and Express

These are APIs that Node.js Express App will export:

  1. GET all User's        :     /api/v1/users
  2. GET User by ID     :     /api/v1/users/{_id}
  3. POST User             :     /api/v1/users 
  4. PUT User               :     /api/v1/users/{_id} 
  5. DELETE User       :     /api/v1/users/{_id}

Back end project structure:


The package.json file 

A package.json is a JSON file that subsists at the root of a Javascript/Node project. It holds metadata pertinent to the project and is utilized for managing the project's dependencies, scripts, version, and a whole lot more.
{
"name": "node_app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon index.ts",
"seed": "ts-node ./seed/seed.ts",
"lint": "eslint . --ext .ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
"@types/express-fileupload": "^1.1.5",
"@types/jsonwebtoken": "^8.5.0",
"@types/mongoose": "^5.7.20",
"@types/node": "^14.0.1",
"@types/properties-reader": "^2.1.0",
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"confi": "^9.10.1",
"cors": "^2.8.5",
"custom-env": "^2.0.1",
"del": "^6.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.20",
"lokijs": "^1.5.11",
"mocha": "^8.2.1",
"mongodb": "^3.5.7",
"mongoose": "^5.9.14",
"mongoose-seed": "^0.6.0",
"morgan": "^1.10.0",
"multer": "^1.4.2",
"properties-reader": "^2.1.1",
"ts-node": "^8.10.1",
"typescript": "^3.9.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"nodemon": "^2.0.3"
}
}


The db.ts file

import mongoose = require('mongoose');

import UserDoc = require('./models/user.model')
export const connectionString = 'mongodb://localhost/user-dev';

mongoose.connect(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
});

export const models = { UserDoc };


The collection.ts file

const collections = {
users: 'users'
};

export = collections


The user.model.ts file inside the model package

import mongoose from 'mongoose';
import collections from '../collections';
import { User } from '../interface/user.interface';

const schema = new mongoose.Schema(
{
firstName: {
type: String,
},
lastName: {
type: String
},
emailId: {
type: String,
unique: true
}
},
);

schema.set('toJSON', { virtuals: true });

export = mongoose.model<User>(collections.users, schema);


The user.interface.ts file inside the interface package

import { Document } from 'mongoose';

export interface User extends Document{
firstName: string;
lastName: string;
emailId: string;
}


The user.routes.ts file

import { Router, Request, Response } from 'express';
import { updateUser, findById,
deleteUserById,saveUserData,getAllUsers }
from '../services/user.service';

const router = Router();

router.post('/users', (req: Request, res: Response) => {
saveUserData(req, res);
});
router.put('/users/:id', (req: Request, res: Response) => {
updateUser(req, res);
});
router.get('/users', (req: Request, res: Response) => {
getAllUsers(req, res);
});
router.delete('/users/:id', (req: Request, res: Response) => {
deleteUserById(req, res);
});
router.get('/users/:id', (req: Request, res: Response) => {
findById(req, res);
});
export = router


The user.service.ts file

import { Request, Response } from 'express';
import { models } from '../db/db';

const { UserDoc } = models;

//get all user
export async function
getAllUsers(request: Request, response: Response):
Promise<Response> {

try {
const data = await UserDoc.find();
console.log(data)
return response.status(200).send(data);
} catch (error) {
console.log(Error)
return response.status(500).
send({ message: "Technical Error" });
}
}

//Save new user
export async function
saveUserData(request: Request, response: Response):
Promise<Response> {

console.log(request.body)
try {
const data = new UserDoc(request.body);
await data.save();
return response.status(200).send({ data });
} catch (error) {
console.log(error)
return response.status(500).
send({ message: "Technical Error" });
}
}

//get user by id
export async function
findById(request: Request, response: Response):
Promise<Response> {

try {
const data = await UserDoc.findById(request.params.id);
return response.status(200).send(data);
} catch (error) {
console.log(Error)
return response.status(500).
send({ message: "Technical Error" });
}

}

//delete user
export async function
deleteUserById(request: Request, response: Response):
Promise<Response> {

try {
console.log(request.params.id)
const data = await UserDoc.remove({ _id: request.params.id });
console.log(data)
return response.status(200).
send({ data: "Deleted Successfully" });
} catch (error) {
console.log(Error)
return response.status(500).
send({ message: "Technical Error" });
}
}

export async function
updateUser(request: Request, response: Response):
Promise<Response> {

try {
console.log(request.body)
const data = await UserDoc.
findOneAndUpdate({ _id: request.params.id },
request.body, { new: true });
console.log(data)
return response.status(200).
send({ data: "Successfully Updated" });
} catch (error) {
console.log(Error)
return response.status(500).
send({ message: "Technical Error" });
}
}


The index.ts file

import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import user from './routes/user.routes';
const morgan = require('morgan');
const fileUpload = require('express-fileupload');
const fs = require('fs');
//if (process.env.NODE_ENV !== 'production') {
//require('dotenv').config();
//}

const app = express();
app.use(fileUpload({
createParentPath: true
}));

app.use(morgan('common', {
stream: fs.createWriteStream('./' + new Date().
toISOString().substr(0, 10)
+ '.log', { flags: 'a' })
}));
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());
app.use('/api/v1/', user)
app.listen(8081, () => {
// eslint-disable-next-line no-console
console.log('App listening on port 8081!');
});


Download the dependencies using the following command

npm install

Run our backend application using the following command

npm start



PART 2 - UI development using Vue.js

Now we are going to develop a Vue.js web application. 

package structure - Front end




The package.json file

{
"name": "frontend-vuejs",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.6.6",
"vue-router": "^3.0.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.5.0",
"@vue/cli-plugin-eslint": "^3.5.0",
"@vue/cli-service": "^3.5.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.5.21"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}


Components

Vue Components are one of the important features of VueJS that creates custom elements, which can be reused in HTML

User.vue

<template>
<div>
<h3>User</h3>
<div class="container">
<form @submit="validateAndSubmit">
<div v-if="errors.length">
<div
class="alert alert-danger"
v-bind:key="index"
v-for="(error, index) in errors"
>
{{ error }}
</div>
</div>
<fieldset class="form-group">
<label>First Name</label>
<input type="text" class="form-control"
v-model="firstName" />
</fieldset>
<fieldset class="form-group">
<label>Last Name</label>
<input type="text" class="form-control"
v-model="lastName" />
</fieldset>
<fieldset class="form-group">
<label>Email Id</label>
<input type="text" class="form-control"
v-model="emailId" />
</fieldset>
<button class="btn btn-success" type="submit">Save</button>
</form>
</div>
</div>
</template>
<script>
import UserDataService from "../service/UserDataService";

export default {
name: "User",
data() {
return {
firstName: "",
lastName: "",
emailId: "",
errors: [],
};
},
computed: {
id() {
return this.$route.params.id;
},
},
methods: {
refreshUserDetails() {
UserDataService.retrieveUser(this.id).then((res) => {
this.firstName = res.data.firstName;
this.lastName = res.data.lastName;
this.emailId = res.data.emailId;
});
},
validateAndSubmit(e) {
e.preventDefault();
this.errors = [];
if (!this.firstName) {
this.errors.push("Enter valid values");
} else if (this.firstName.length < 5) {
this.errors.
push("Enter atleast 5 characters in First Name");
}
if (!this.lastName) {
this.errors.push("Enter valid values");
} else if (this.lastName.length < 5) {
this.errors.
push("Enter atleast 5 characters in Last Name");
}
if (this.errors.length === 0) {
if (this.id == -1) {
UserDataService.createUser({
firstName: this.firstName,
lastName: this.lastName,
emailId: this.emailId,
}).then(() => {
this.$router.push("/users");
});
} else {
UserDataService.updateUser(this.id, {
id: this.id,
firstName: this.firstName,
lastName: this.lastName,
emailId: this.emailId,
}).then(() => {
this.$router.push("/users");
});
}
}
},
},
created() {
this.refreshUserDetails();
},
};
</script>


Users.vue

<template>
<div class="container">
<h3>All Users</h3>
<div v-if="message" class="alert alert-success">
{{ this.message }}</div>
<div class="container">
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email Id</th>
<th>Update</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" v-bind:key="user.id">
<td>{{ user.firstName }}</td>
<td>{{ user.lastName }}</td>
<td>{{ user.emailId }}</td>
<td>
<button class="btn btn-warning"
v-on:click="updateUser(user.id)">
Update
</button>
</td>
<td>
<button class="btn btn-danger"
v-on:click="deleteUser(user.id)">
Delete
</button>
</td>
</tr>
</tbody>
</table>
<div class="row">
<button class="btn btn-success"
v-on:click="addUser()">Add</button>
</div>
</div>
</div>
</template>
<script>
import UserDataService from "../service/UserDataService";

export default {
name: "Users",
data() {
return {
users: [],
message: "",
};
},
methods: {
refreshUsers() {
UserDataService.retrieveAllUsers().then((res) => {
this.users = res.data;
});
},
addUser() {
this.$router.push(`/user/-1`);
},
updateUser(id) {
this.$router.push(`/user/${id}`);
},
deleteUser(id) {
UserDataService.deleteUser(id).then(() => {
this.refreshUsers();
});
},
},
created() {
this.refreshUsers();
},
};
</script>


UserDataService.js

import axios from 'axios'

const USER_API_URL = 'http://localhost:8081/api/v1'

class UserDataService {

retrieveAllUsers() {

return axios.get(`${USER_API_URL}/users`);
}

retrieveUser(id) {

return axios.get(`${USER_API_URL}/users/${id}`);
}

deleteUser(id) {

return axios.delete(`${USER_API_URL}/users/${id}`);
}

updateUser(id, user) {

return axios.put(`${USER_API_URL}/users/${id}`, user);
}

createUser(user) {

return axios.post(`${USER_API_URL}/users`, user);
}
}

export default new UserDataService()


App.vue

<template>
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Node.js + Mongo DB +
Vue.js CRUD Application</a><br/><br/>
</div>
<router-view/>
</div>
</template>

<script>
export default {
name: "app"
};
</script>

<style>
@import url(https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css);
</style>


routes.js

import Vue from "vue";
import Router from "vue-router";

Vue.use(Router);

const router = new Router({
mode: 'history',
routes: [
{
path: "/",
name: "Users",
component: () => import("./components/Users"),
},
{
path: "/users",
name: "Users",
component: () => import("./components/Users"),
},
{
path: "/user/:id",
name: "User",
component: () => import("./components/User"),
},
]
});

export default router;


main.js

import Vue from 'vue'
import App from './App.vue'
import router from './routes';

Vue.config.productionTip = false

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


## Install the dependencies

npm install

### Run the application in local

npm run serve

 App running at:- Local:   http://localhost:8080/ 

Download Source code

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete