MEVN Stack Tutorial: The Complete Guide Step by Step

MEVN stack is the open-source JavaScript software stack that has emerged as a new and evolving way to build robust and dynamic web applications.

npm install -g @vue/cli

# OR

yarn global add @vue/cli

You can check the version of Vue.js using this command.

vue --version

Here are the steps to create a MEVN stack application.

Step 1: Create a Vue.js project

Type the following command to install Vue.js.

vue create mevnexample 

MEVN Stack Tutorial With Example From Scratch

Inside the vue project.

cd mevnexample

Open the project inside the code editor. I am using Visual Code.

code .

Start the vue dev server using the following command.

npm run serve

Go to http://localhost:8080.

You can see that we have successfully installed Vue.js.

Step 2: Install Vue dependencies.

Type the following command to install the axiosvue-router, and vue-axios dependencies. The vue-router is used for routing our Vue.js application to use the different components, and the vue-axios is for sending the network request to the server.

npm install axios vue-router vue-axios --save

Install the Bootstrap CSS Framework version 4 using the following command.

npm install bootstrap --save

Import the Bootstrap 4 CSS inside the main.js file.

// main.js

import Vue from 'vue'
import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.min.css'

Vue.config.productionTip = false

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

Step 3: Create the vue components.

Go to the src >> components folder, remove the HelloWorld.vue file and create the following components.

  1. HomeComponent.vue
  2. CreateComponent.vue
  3. EditComponent.vue
  4. IndexComponent.vue

Add the following code inside the HomeComponent.vue file.

<template>
  <div class="row justify-content-center">
      <div class="col-md-8">
          <div class="card card-default">
              <div class="card-header">Home Component</div>

              <div class="card-body">
                  I'm the Home Component component.
              </div>
          </div>
      </div>
  </div>
</template>
<script>
export default {
}
</script>

The next step is to import the HomeComponent.vue file inside the App.vue file.

// App.vue

<template>
  <div>
    <HomeComponent />
  </div>
</template>

<script>
import HomeComponent from './components/HomeComponent.vue'

export default {
  name: 'app',
  components: {
    HomeComponent
  }
}
</script>

Save the file, and if the server is running, you can go to http://localhost:8080 and see that HomeComponent is rendering.

Create the remaining three components.

// CreateComponent.vue

<template>
  <div class="row justify-content-center">
      <div class="col-md-8">
          <div class="card card-default">
              <div class="card-header">Create Component</div>

              <div class="card-body">
                  I'm the Create Component component.
              </div>
          </div>
      </div>
  </div>
</template>

<script>
  export default {
  }
</script>
// EditComponent.vue

<template>
  <div class="row justify-content-center">
      <div class="col-md-8">
          <div class="card card-default">
              <div class="card-header">Edit Component</div>

              <div class="card-body">
                  I'm an Edit component.
              </div>
          </div>
      </div>
  </div>
</template>

<script>
  export default {
  }
</script>
// IndexComponent.vue

<template>
  <div class="row justify-content-center">
      <div class="col-md-8">
          <div class="card card-default">
              <div class="card-header">Index Component</div>

              <div class="card-body">
                  I'm an Index component.
              </div>
          </div>
      </div>
  </div>
</template>

<script>
  export default {
  }
</script>

Step 4: Configure the vue-router

Inside the main.js file, write the following code.

// main.js

import Vue from 'vue'
import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.min.css'

import VueRouter from 'vue-router';
Vue.use(VueRouter);

Vue.config.productionTip = false;

import HomeComponent from './components/HomeComponent.vue';
import CreateComponent from './components/CreateComponent.vue';
import IndexComponent from './components/IndexComponent.vue';
import EditComponent from './components/EditComponent.vue';

const routes = [
  {
      name: 'home',
      path: '/',
      component: HomeComponent
  },
  {
      name: 'create',
      path: '/create',
      component: CreateComponent
  },
  {
      name: 'posts',
      path: '/posts',
      component: IndexComponent
  },
  {
      name: 'edit',
      path: '/edit/:id',
      component: EditComponent
  }
];

const router = new VueRouter({ mode: 'history', routes: routes});

new Vue(Vue.util.extend({ router }, App)).$mount('#app');

Add the <router-view> inside the App.vue file.

// App.vue

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

<script>


export default {
  name: 'app'
}
</script>

Save the file and check the following route paths to see if we get the exact routes.

  1. http://localhost:8080/create
  2. http://localhost:8080/posts
  3. http://localhost:8080/edit/21

Step 5: Create a Navigation bar

Write the following code inside the App.vue file. I have added the navbar.

// App.vue

<template>
  <div class="container">
    <nav class="navbar navbar-expand-sm bg-dark navbar-dark">
      <ul class="navbar-nav">
        <li class="nav-item">
          <router-link to="/" class="nav-link">Home</router-link>
        </li>
        <li class="nav-item">
          <router-link to="/create" class="nav-link">Create Post</router-link>
        </li>
        <li class="nav-item">
          <router-link to="/posts" class="nav-link">Posts</router-link>
        </li>
      </ul>
    </nav><br />
    <transition name="fade">
      <router-view></router-view>
    </transition>
  </div>
</template>

<style>
    .fade-enter-active, .fade-leave-active {
      transition: opacity .5s
    }
    .fade-enter, .fade-leave-active {
      opacity: 0
    }
</style>

<script>

    export default{
    }
</script>

What we have done is add the navigation bar and add some animation effects like transition. So when we navigate the routes, the component changes according to their path.

Vue Node CRUD Example

Step 6: Create a Form

We need to create a form to enter the details of the form. So let us create a form inside the CreateComponent.vue file.

// CreateComponent.vue

<template>
  <div>
    <h1>Create A Post</h1>
    <form @submit.prevent="addPost">
      <div class="row">
        <div class="col-md-6">
          <div class="form-group">
            <label>Post Title:</label>
            <input type="text" class="form-control" v-model="post.title">
          </div>
        </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group">
              <label>Post Body:</label>
              <textarea class="form-control" v-model="post.body" rows="5"></textarea>
            </div>
          </div>
        </div><br />
        <div class="form-group">
          <button class="btn btn-primary">Create</button>
        </div>
    </form>
  </div>
</template>

<script>
    export default {
        data(){
        return {
          post:{}
        }
    },
    methods: {
      addPost(){
        console.log(this.post);
      }
    }
  }
</script>

Save the file and go to this URL: http://localhost:8080/create or /create. Then you can see the form below.

Vue Express CRUD Tutorial Example

Step 7: Create a Node.js backend.

Create one folder inside the vue project root called api and go inside that folder.

Initialize the package.json file.

npm init -y

Install the following node.js dependencies. 

yarn add express body-parser cors mongoose

# or

npm install express body-parser cors mongoose --save

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

npm install nodemon --save-dev

Create three files called server.js, DB.js, post.model.js, and post.route.js. All these files are inside the root of the api folder. For example, write the following code in the server.js file.

// server.js

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const PORT = 4000;
const cors = require('cors');

app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.listen(PORT, function(){
  console.log('Server is running on Port:',PORT);
});

We need to start a node server using the following command.

nodemon server

Our node.js server is running at port: 4000.

Vue Node Tutorial Example

Step 8: Set up and connect the MongoDB database.

mongod

Inside an api folder, create one file called DB.js, which connects our node application to the mongodb database. So add the following line of code.

// DB.js

module.exports = {
    DB: 'mongodb://localhost:27017/mevncrud'
}

In my local MongoDB database, the username and password are empty, but in the production database, you must create one user and assign that user to the database.

Import this DB.js file into the server.js file.

// server.js

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const PORT = 4000;
const cors = require('cors');
const mongoose = require('mongoose');
const config = require('./DB.js');

mongoose.Promise = global.Promise;
mongoose.connect(config.DB, { useNewUrlParser: true }).then(
  () => {console.log('Database is connected') },
  err => { console.log('Can not connect to the database'+ err)}
);

app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.listen(PORT, function(){
  console.log('Server is running on Port:',PORT);
});

Save the file, and you can see inside the terminal that our node.js application is connected to the mongodb database.

Step 9: Create a Mongoose Schema

Write the following code inside the post.model.js file.

// post.model.js

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

// Define collection and schema for Post
let Post = new Schema({
  title: {
    type: String
  },
  body: {
    type: String
  }
},{
    collection: 'posts'
});

module.exports = mongoose.model('Post', Post);

We have taken three fields called title and body with the String data type.

Step 10: Define a route for the Node.js Express application

Write the CRUD operation code inside the post.route.js file.

// post.model.js

const express = require('express');
const postRoutes = express.Router();

// Require Post model in our routes module
let Post = require('./post.model');

// Defined store route
postRoutes.route('/add').post(function (req, res) {
  let post = new Post(req.body);
  post.save()
    .then(() => {
      res.status(200).json({'business': 'business in added successfully'});
    })
    .catch(() => {
      res.status(400).send("unable to save to database");
    });
});

// Defined get data(index or listing) route
postRoutes.route('/').get(function (req, res) {
    Post.find(function(err, posts){
    if(err){
      res.json(err);
    }
    else {
      res.json(posts);
    }
  });
});

// Defined edit route
postRoutes.route('/edit/:id').get(function (req, res) {
  let id = req.params.id;
  Post.findById(id, function (err, post){
      if(err) {
        res.json(err);
      }
      res.json(post);
  });
});

//  Defined update route
postRoutes.route('/update/:id').post(function (req, res) {
    Post.findById(req.params.id, function(err, post) {
    if (!post)
      res.status(404).send("data is not found");
    else {
        post.title = req.body.title;
        post.body = req.body.body;
        post.save().then(() => {
          res.json('Update complete');
      })
      .catch(() => {
            res.status(400).send("unable to update the database");
      });
    }
  });
});

// Defined delete | remove | destroy route
postRoutes.route('/delete/:id').delete(function (req, res) {
    Post.findByIdAndRemove({_id: req.params.id}, function(err){
        if(err) res.json(err);
        else res.json('Successfully removed');
    });
});

module.exports = postRoutes;

Here, we have defined the CRUD operations in Node.js, the backend server-side. So when the request from the clientside hits the node express server, it maps the URI, and according to URI, the above function will be executed. Database operation will be performed, and send the response to the client; in our case, it is the vue.js front.

We have used a Mongoose ORM to create, read, update, and delete the data from the mongodb database. Mongoose is an ORM(Object Relational Mapping) used in the MongoDB database. We have all the CRUD operations on the post.route.js file; we must import them into the server.js file.

The final server.js file looks like this.

// server.js

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const PORT = 4000;
const cors = require('cors');
const mongoose = require('mongoose');
const config = require('./DB.js');
const postRoute = require('./post.route');

mongoose.Promise = global.Promise;
mongoose.connect(config.DB, { useNewUrlParser: true }).then(
  () => { console.log('Database is connected') },
  err => { console.log('Can not connect to the database'+ err)}
);

app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.use('/posts', postRoute);

app.listen(PORT, function(){
  console.log('Server is running on Port:',PORT);
});
If the linter shows an error on the console statement inside the Visual Studio Code editor or other editors, ignore it because it is a warning provided by the eslint configuration. It is not an actual error; you can see the output inside the browser.

Step 11: Use axios to send a network request

Go to the frontend vue project and import the axios and vue-axios inside the main.js file.

// main.js

import Vue from 'vue'
import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.min.css'

import VueRouter from 'vue-router';
Vue.use(VueRouter);

import VueAxios from 'vue-axios';
import axios from 'axios';

Vue.use(VueAxios, axios);

Vue.config.productionTip = false;

import HomeComponent from './components/HomeComponent.vue';
import CreateComponent from './components/CreateComponent.vue';
import IndexComponent from './components/IndexComponent.vue';
import EditComponent from './components/EditComponent.vue';

const routes = [
  {
      name: 'home',
      path: '/',
      component: HomeComponent
  },
  {
      name: 'create',
      path: '/create',
      component: CreateComponent
  },
  {
      name: 'posts',
      path: '/posts',
      component: IndexComponent
  },
  {
      name: 'edit',
      path: '/edit/:id',
      component: EditComponent
  }
];

const router = new VueRouter({ mode: 'history', routes: routes});

new Vue(Vue.util.extend({ router }, App)).$mount('#app');

We have created the backend. The next step is to send the POST request to the node.js api server. Remember, now we have three servers running.

  1. Vue development server
  2. Node.js server
  3. MongoDB server

Remember, all are running fine; otherwise, the project won’t work.

Write the following code inside the CreateComponent.vue file’s addPost() function.

// CreateComponent.vue

addPost(){
    let uri = 'http://localhost:4000/posts/add';
    this.axios.post(uri, this.post).then(() => {
       this.$router.push({name: 'posts'});
    });
}

You can now create the post and see it in the mongodb database.

To check from the terminal, you must open the mongoshell using the following command.

mongo

MongoDB database example

Step 12: Display the backend data.

Write the following code inside the IndexComponent.js file.

// IndexComponent.js

<template>
  <div>
      <h1>Posts</h1>
        <div class="row">
          <div class="col-md-10"></div>
          <div class="col-md-2">
            <router-link :to="{ name: 'create' }" class="btn btn-primary">Create Post</router-link>
          </div>
        </div><br />

        <table class="table table-hover">
            <thead>
            <tr>
              <th>Title</th>
              <th>Body</th>
              <th>Actions</th>
            </tr>
            </thead>
            <tbody>
                <tr v-for="post in posts" :key="post._id">
                  <td>{{ post.title }}</td>
                  <td>{{ post.body }}</td>
                  <td><router-link :to="{name: 'edit', params: { id: post._id }}" class="btn btn-primary">Edit</router-link></td>
                  <td><button class="btn btn-danger">Delete</button></td>
                </tr>
            </tbody>
        </table>
  </div>
</template>

<script>
  export default {
      data() {
        return {
          posts: []
        }
      },
      created() {
      let uri = 'http://localhost:4000/posts';
      this.axios.get(uri).then(response => {
        this.posts = response.data;
      });
    }
  }
</script>

When the component is created, it will send a GET request to the node.js server, fetch the data from the database, assign that data to the posts array, and then loop through that array and display the data in the tabular format.

MEVN CRUD Example

Step 13: Send edit and update request

When our edit component loads, we need to fetch the data from the server to display the existing data.

Then, after changing the data in the textbox and textarea, we hit the update button, and we call the updatePost() function to send a post request to the server to update the data.

// EditComponent.vue

<template>
  <div>
    <h1>Edit Post</h1>
    <form @submit.prevent="updatePost">
      <div class="row">
        <div class="col-md-6">
          <div class="form-group">
            <label>Post Title:</label>
            <input type="text" class="form-control" v-model="post.title">
          </div>
        </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group">
              <label>Post Body:</label>
              <textarea class="form-control" v-model="post.body" rows="5"></textarea>
            </div>
          </div>
        </div><br />
        <div class="form-group">
          <button class="btn btn-primary">Update</button>
        </div>
    </form>
  </div>
</template>

<script>
    export default {

      data() {
        return {
          post: {}
        }
      },
      created() {
        let uri = `http://localhost:4000/posts/edit/${this.$route.params.id}`;
        this.axios.get(uri).then((response) => {
            this.post = response.data;
        });
      },
      methods: {
        updatePost() {
          let uri = `http://localhost:4000/posts/update/${this.$route.params.id}`;
          this.axios.post(uri, this.post).then(() => {
            this.$router.push({name: 'posts'});
          });
        }
      }
    }
</script>

You will see the edited data from the database, and also you can update the database. So till now, insert, read,  and update operations are complete. Now, last, the delete is remaining.

Step 14: Delete or Remove the data.

The only remaining thing is to delete or remove the data from the database.

Let us write the final code inside the IndexComponent.vue file.

// IndexComponent.vue

<template>
  <div>
      <h1>Posts</h1>
        <div class="row">
          <div class="col-md-10"></div>
          <div class="col-md-2">
            <router-link :to="{ name: 'create' }" class="btn btn-primary">Create Post</router-link>
          </div>
        </div><br />

        <table class="table table-hover">
            <thead>
            <tr>
              <th>Title</th>
              <th>Body</th>
              <th>Actions</th>
            </tr>
            </thead>
            <tbody>
                <tr v-for="post in posts" :key="post._id">
                  <td>{{ post.title }}</td>
                  <td>{{ post.body }}</td>
                  <td><router-link :to="{name: 'edit', params: { id: post._id }}" class="btn btn-primary">Edit</router-link></td>
                  <td><button class="btn btn-danger" @click.prevent="deletePost(post._id)">Delete</button></td>
                </tr>
            </tbody>
        </table>
  </div>
</template>

<script>
  export default {
      data() {
        return {
          posts: []
        }
      },
      created() {
      let uri = 'http://localhost:4000/posts';
      this.axios.get(uri).then(response => {
        this.posts = response.data;
      });
    },
    methods: {
      deletePost(id)
      {
        let uri = `http://localhost:4000/posts/delete/${id}`;
        this.axios.delete(uri).then(response => {
          this.posts.splice(this.posts.indexOf(id), 1);
        });
      }
    }
  }
</script>

Save the file; now, you can delete the values from the MongoDB database.

Our MEVN Stack Tutorial With Example Demo is over. However, I have put this code on Github. So check that out as well.

Github Code

That’s it!

10 thoughts on “MEVN Stack Tutorial: The Complete Guide Step by Step”

  1. This is a bug. This code deletes from the end of the list until a refresh occurs.

    this.posts.splice(this.posts.indexOf(id), 1);

    Reply
  2. ERROR Failed to compile with 1 errors 4:45:15 PM

    error in ./src/components/IndexComponent.vue

    Module Error (from ./node_modules/eslint-loader/index.js):
    error: ‘response’ is defined but never used (no-unused-vars) at src\components\IndexComponent.vue:48:37:
    46 | {
    47 | let uri = `http://localhost:4000/posts/delete/${id}`;
    > 48 | this.axios.delete(uri).then(response => {
    | ^
    49 | this.posts.splice(this.posts.findIndex(i => i._id == id), 1); });
    50 | }
    51 | }

    Reply
  3. The delete statement is not working. I have tried the solutions suggested by the comment section and it is still not working. any other suggestions?

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.