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:
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 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:
- GET all User's : /api/v1/users
- GET User by ID : /api/v1/users/{_id}
- POST User : /api/v1/users
- PUT User : /api/v1/users/{_id}
- 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 userexport 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 userexport 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 idexport 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 userexport 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/