Vue 2 MEVN Stack Tutorial – Build Full Stack Vue CRUD App

Last Updated on by in Vue JS
This is a step by step MEVN stack tutorial, in this tutorial, we are going to learn how to create MEVN stack app. (MongoDB, Express.js, Vue.js, Node.js).Build MEVN Stack CRUD Application

I will show you how to create a Full-stack single page application with Vue from scratch.

We will create a simple yet best Student Record Management system. This system efficiently allows users to perform CRUD (CREATE, READ, UPDATE & DELETE) operations.

We will create our server using Node and Express.js and store student records. We will use MongoDB. We will manage the front-end of the application with Vue.js.

So, let us start coding the MEVN Stack app with a practical example.

Vue JS 2 Full Stack CRUD Example

  • Step 1: Create a New Vue Project
  • Step 2: Adding Bootstrap in Vue
  • Step 3: Build Vue Components
  • Step 4: Enable Vue Router
  • Step 5: Setting Up Navigation and Router View
  • Step 6: Add Axios in Vue
  • Step 7: Build Form in Vue with Bootstrap
  • Step 8: Setting up Node Server Environment
  • Step 9: Create Mongoose Model
  • Step 10: Create Route in Node/Express App
  • Step 11: Start Node/Express App
  • Step 12: Create Student Data with Axios POST
  • Step 13: Show Data List & Delete Data in Vue
  • Step 14: Update Data with POST Request
  • Step 15: Summary

Create a New Vue Project

To install Vue project, we must have Vue CLI installed on our development system.

Run the following command to install Vue CLI:

# npm
npm install -g @vue/cli

# yarn
yarn global add @vue/cli

Use the following command to install the vue project.

vue create vue-mevn-stack-app

Head over to vue project:

cd vue-mevn-stack-app

To remove multi-word error warning, add the following code in vue.config.js file.

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false,
})

Node.js Gatsby error – “digital envelope routines::unsupported …”

Error: digital envelope routines::unsupported

opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
library: 'digital envelope routines',
reason: 'unsupported',
code: 'ERR_OSSL_EVP_UNSUPPORTED'

To remove above error for invoking the app, make sure to update the "scripts": [] array in package.json file.

"scripts": {
    "serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
    "build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
    "lint": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service lint"
},

Run command to start the app on the browser:

npm run serve

Adding Bootstrap in Vue

Let us run the below command to install the Bootstrap UI Framework.

npm install bootstrap

Import the Bootstrap framework path inside the main.js file. Now, you can use Bootstrap UI components in your vue app.

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

import 'bootstrap/dist/css/bootstrap.min.css'

Vue.config.productionTip = false

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

Build Vue Components

Head over to src/components directory, here we have to create the following components. These components will handle the data in our full-stack Vue.Js application.

  • CreateComponent.vue
  • EditComponent.vue
  • ListComponent.vue

Open src/CreateComponent.vue file and add the following code inside of it.

<template>
    <div class="row justify-content-center">
        <div class="col-md-6">
            <!-- Content goes here -->
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
            }
        }
    }
</script>
</div>

Open src/EditComponent.vue file and place code inside of it.

<template>
    <div class="row justify-content-center">
        <div class="col-md-6">
            <!-- Update Student content -->
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
            }
        }
    }
</script>

Open src/ListComponent.vue file and add the below code in it.

<template>
    <div class="row justify-content-center">
        <div class="col-md-6">
            <!-- Display Student List -->
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
            }
        }
    }
</script>

Enable Vue Router

Install the vue router package in vue app:

npm add vue-router@^3.1.6

Open src/router/index.js and replace the existing code with the following code.

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('../components/CreateComponent')
  },
  {
    path: '/view',
    name: 'view',
    component: () => import('../components/ListComponent')
  },
  {
    path: '/edit/:id',
    name: 'edit',
    component: () => import('../components/EditComponent')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

Create Routes in Vue

Go to src/App.vue file, here we define the Bootstrap Navigation component, router-view directive and the router-link directive.

<template>
  <div>
    <!-- Nav bar -->
    <nav class="navbar navbar-dark bg-primary justify-content-between flex-nowrap flex-row">
      <div class="container">
        <a class="navbar-brand float-left">MEVN Stack Example</a>
        <ul class="nav navbar-nav flex-row float-right">
          <li class="nav-item">
            <router-link class="nav-link pr-3" to="/">Create Student</router-link>
          </li>
          <li class="nav-item">
            <router-link class="nav-link" to="/view">View Students</router-link>
          </li>
        </ul>
      </div>
    </nav>

    <!-- Router view -->
    <div class="container mt-5">
      <router-view></router-view>
    </div>
  </div>
</template>

Add Axios in Vue to Handle HTTP Requests

Run command to install Axios:

npm install axios

Build Form in Vue with Bootstrap 5

We require to store the data in the MEVN stack app, so we need to build a vue form using Bootstrap Form component.

Check out our previous tutorial detailed tutorial on – Form Validation in Vue with Vuelidate.

Open components/CreateComponent.vue file, then place the following code in it.

<template>
    <div class="row justify-content-center">
        <div class="col-md-6">
            <h3 class="text-center">Create Student</h3>
            <form @submit.prevent="handleSubmitForm">
                <div class="mb-3">
                    <label>Name</label>
                    <input type="text" class="form-control" v-model="student.name" required>
                </div>

                <div class="mb-3">
                    <label>Email</label>
                    <input type="email" class="form-control" v-model="student.email" required>
                </div>

                <div class="mb-3">
                    <label>Phone</label>
                    <input type="text" class="form-control" v-model="student.phone" required>
                </div>

                <div class="mb-3">
                    <button class="btn btn-danger btn-block">Create</button>
                </div>
            </form>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                student: {
                   name: '',
                   email: '',
                   phone: ''
                }
            }
        },
        methods: {
            handleSubmitForm() { }
        }
    }
</script>

We created a basic form with name, email and phone number field. We created a beautiful form using Bootstrap form component.

The student object works with two-way data binding approach; it merely means that any data-related changes affecting the model are immediately propagated to the matching view.

Here is the form which you can check in the browser.

Build Form in Vue with Bootstrap

Setting up Node Server Environment

Now, we need to create REST APIs using Node + Express & MongoDB in Vue application. Create backend folder at the root of Vue project.

mkdir backend 

Generate separate package.json for node server.

npm init -y

Run command to install the following dependencies for Node/Express js.

npm i body-parser cors express mongoose 

Also, install a nodemon server as a development dependency. So that we do not need to restart every time, we change our server code.

Install nodemon as a development dependency, It automates the server starting process.

npm install nodemon --save-dev

Create Mongoose Model

Create models/Student.js and paste the below code inside the file. We will define name, email and phone values inside the Student model.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

let studentSchema = new Schema({
  name: {
    type: String
  },
  email: {
    type: String
  },
  phone: {
    type: Number
  },
}, {
  collection: 'students'
})

module.exports = mongoose.model('Student', studentSchema)

Create Route in Node/Express App

Create a backend/routes directory; here, we have to create student.route.js file and place all the given below code inside of it.

const express = require("express");
const studentRoute = express.Router();
let StudentModel = require("../models/Student");
const cors = require("cors");

// CORS OPTIONS
var whitelist = ["http://localhost:4200", "http://localhost:8000"];
var corsOptionsDelegate = function (req, callback) {
  var corsOptions;
  if (whitelist.indexOf(req.header("Origin")) !== -1) {
    corsOptions = {
      origin: "*",
      methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
    };
  } else {
    corsOptions = { origin: false }; // disable CORS for this request
  }
  callback(null, corsOptions);
};

studentRoute.route("/create-student").post(async (req, res, next) => {
  await StudentModel.create(req.body)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully added!",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

studentRoute
  .route("/", cors(corsOptionsDelegate))
  .get(async (req, res, next) => {
    await StudentModel.find()
      .then((result) => {
        res.writeHead(201, { "Content-Type": "application/json" });
        res.end(JSON.stringify(result));
      })
      .catch((err) => {
        return next(err);
      });
  });

studentRoute.route("/edit-student/:id").get(async (req, res, next) => {
  await StudentModel.findById(req.params.id, req.body)
    .then((result) => {
      res.json({
        data: result,
        message: "Data successfully retrieved.",
        status: 200,
      });
    })
    .catch((err) => {
      return next(err);
    });
});

// Update
studentRoute.route("/update-student/:id").put(async (req, res, next) => {
  await StudentModel.findByIdAndUpdate(req.params.id, {
    $set: req.body,
  })
    .then((result) => {
      res.json({
        data: result,
        msg: "Data successfully updated.",
      });
    })
    .catch((err) => {
      console.log(err);
    });
});

// Delete
studentRoute.route("/delete-student/:id").delete(async (req, res) => {
  await StudentModel.findByIdAndRemove(req.params.id)
    .then(() => {
      res.json({
        msg: "Data successfully updated.",
      });
    })
    .catch((err) => {
      console.log(err);
    });
});

module.exports = studentRoute;

Create the backend/index.js file and place the following code that contains the Node server settings.

const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");

// Connecting MongoDB
async function mongoDbConnection() {
  await mongoose.connect(
    "mongodb://127.0.0.1:27017/test",
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    },
    6000,
  );
}
mongoDbConnection().then(() => {
  console.log("MongoDB successfully connected.");
}),
  (err) => {
    console.log("Could not connected to database : " + err);
  };


const studentRoute = require("./routes/student.route");
const app = express();
app.use(bodyParser.json());
app.use(
  bodyParser.urlencoded({
    extended: false,
  })
);
app.use(cors());
// API
app.use("/api", studentRoute);
// Create port
const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
  console.log("Connected to port " + port);
});
// Find 404
app.use((req, res, next) => {
  next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Start Node/Express App

We have built following API using Node and Express in Vue.js.

Method API
GET http://localhost:4000/api
POST /api/create-student
GET /api/edit-student/:id
POST /api/update-student/:id
DELETE /api/delete-student/:id

Start Node Server:

Make sure to setup and follow the guide to start the MongoDB community and MongoDB Compass GUI database in your local system.

Open a new terminal window and start the nodemon server:

npx nodemon server

You can view server running on http://localhost:4000/api

Open another terminal window and start the vue app:

npm run serve

You can view vue app running on http://localhost:8080

Create Student Data with Axios POST

The Axios post method takes the REST API and makes the POST request to the server. It creates the student data that we are adding in the mongoDB database.

Once the data is sent to the server, you can then check the stored data on http://localhost:4000/api

Add the given below code inside the components/CreateComponent.vue file.

<template>
    <div class="row justify-content-center">
        <div class="col-md-6">
            <h3 class="text-center">Create Student</h3>
            <form @submit.prevent="handleSubmitForm">
                <div class="mb-3">
                    <label>Name</label>
                    <input type="text" class="form-control" v-model="student.name" required>
                </div>

                <div class="mb-3">
                    <label>Email</label>
                    <input type="email" class="form-control" v-model="student.email" required>
                </div>

                <div class="mb-3">
                    <label>Phone</label>
                    <input type="text" class="form-control" v-model="student.phone" required>
                </div>

                <div class="mb-3">
                    <button class="btn btn-danger btn-block">Create</button>
                </div>
            </form>
        </div>
    </div>
</template>

<script>
    import axios from "axios";

    export default {
        data() {
            return {
                student: {
                   name: '',
                   email: '',
                   phone: ''
                }
            }
        },
        methods: {
            handleSubmitForm() {
                let apiURL = 'http://localhost:4000/api/create-student';
                
                axios.post(apiURL, this.student).then(() => {
                  this.$router.push('/view')
                  this.student = {
                    name: '',
                    email: '',
                    phone: ''
                  }
                }).catch(error => {
                    console.log(error)
                });
            }
        }
    }
</script>

Show Data List & Delete Data in Vue

Now, we will show the data in the tabular form using Bootstrap Table components, and we will make the axios.get() request to render the data from the server.

Add the given below code inside the components/ListComponent.vue file.

<template>
    <div class="row">
        <div class="col-md-12">
            <table class="table table-striped">
                <thead class="thead-dark">
                    <tr>
                        <th>Name</th>
                        <th>Email</th>
                        <th>Phone</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="student in Students" :key="student._id">
                        <td>{{ student.name }}</td>
                        <td>{{ student.email }}</td>
                        <td>{{ student.phone }}</td>
                        <td>
                            <router-link :to="{name: 'edit', params: { id: student._id }}" class="btn btn-success">Edit
                            </router-link>
                            <button @click.prevent="deleteStudent(student._id)" class="btn btn-danger">Delete</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script>
    import axios from "axios";

    export default {
        data() {
            return {
                Students: []
            }
        },
        created() {
            let apiURL = 'http://localhost:4000/api';
            axios.get(apiURL).then(res => {
                this.Students = res.data;
            }).catch(error => {
                console.log(error)
            });
        },
        methods: {
            deleteStudent(id){
                let apiURL = `http://localhost:4000/api/delete-student/${id}`;
                let indexOfArrayItem = this.Students.findIndex(i => i._id === id);

                if (window.confirm("Do you really want to delete?")) {
                    axios.delete(apiURL).then(() => {
                        this.Students.splice(indexOfArrayItem, 1);
                    }).catch(error => {
                        console.log(error)
                    });
                }
            }
        }
    }
</script>

<style>
    .btn-success {
        margin-right: 10px;
    }
</style>

To delete the student object from the database, we defined the deleteStudent() function and bound it to click event with an id parameter.

Show Data List & Delete Data in Vue

Update Data with POST Request

Add the following code inside the components/EditComponent.vue file.

<template>
  <div class="row justify-content-center">
    <div class="col-md-6">
      <h3 class="text-center">Update Student</h3>
      <form @submit.prevent="handleUpdateForm">
        <div class="mb-3">
          <label>Name</label>
          <input type="text" class="form-control" v-model="student.name" required>
        </div>
        <div class="mb-3">
          <label>Email</label>
          <input type="email" class="form-control" v-model="student.email" required>
        </div>
        <div class="mb-3">
          <label>Phone</label>
          <input type="text" class="form-control" v-model="student.phone" required>
        </div>
        <div class="mb-3">
          <button class="btn btn-danger btn-block">Update</button>
        </div>
      </form>
    </div>
  </div>
</template>
<script>
import axios from "axios";
export default {
  data() {
    return {
      student: {}
    }
  },
  created() {
    let apiURL = `http://localhost:4000/api/edit-student/${this.$route.params.id}`;
    axios.get(apiURL).then((res) => {
      this.student = res.data.data;
    })
  },
  methods: {
    handleUpdateForm() {
      let apiURL = `http://localhost:4000/api/update-student/${this.$route.params.id}`;
      axios.put(apiURL, this.student).then((res) => {
        console.log(res)
        this.$router.push('/view')
      }).catch(error => {
        console.log(error)
      });
    }
  }
}
</script>

Summary

In the final step of this MEVN Stack tutorial, follow the mentioned instructions to start and test the app.

You can download the complete code of this tutorial from GitHub.

Make sure to bless me with a star if this guide helped you!