MEVN Stack Tutorial: The Complete Guide Step by Step

In this tutorial, we will generate FullStack Single Page Application(SPA) using MongoDB, Express, Vue, and Node.js CRUD Example Tutorial step by step. For this example, we will not use Vuex for state management because this app will be simple and not complicated. We will create an application to create, update, read, and delete the post. So, let us start the MEVN Stack Tutorial with an example.

MEVN Stack

We install the Vue.js project using Vue CLI. Vue CLI requires the Node.js version 8.9 or above (8.11.0+ recommended). You can manage multiple versions of Node on the same machine with nvm or nvm-windows

Type the following command to install Vue CLI if you have not installed it.

npm install -g @vue/cli

# OR

yarn global add @vue/cli

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

vue --version

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

Now, go 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 the http://localhost:8080.

You can see that we have successfully installed the 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 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

Now, 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 and remove the HelloWorld.vue file, and create the following components.

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

Now, 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>

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.

Now, 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');

What we have done is that, first, imported the vue-router module and then create an array of routes that has a name, path, and components as the properties.

Then we have created the router object and passed the mode history and the routes array. So when the application boot, we will give these routes to the vue application.

Now, the next step is to define the <router-view></router-view> element. It renders the component according to the routing path in the Vue application.

So, 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 are getting 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>

So, what we have done is add the navigation bar and add some animation effects like transition. So when we navigate the routes, the component is changing 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>

Also, we have defined the object called post. So here, we have used two-way data binding. The post object has two properties. 1) title 2) body.

We have made one method called addPost().  So, when a user submits the form, we will get the input inside the addPost() method. From then, we will send a POST request to the Laravel server and save the data into the database.

I am skipping the validation of each field because this article is getting long and long. So we will do it in another post.

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.

Now, initialize the package.json file.

npm init -y

Now, install the following node.js dependencies. 

yarn add express body-parser cors mongoose

# or

npm install express body-parser cors mongoose --save

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

We have installed the express web framework, which is built on top of the Node.js server. Also, I installed the body-parser module to parse the data coming from the request and then installed the cors module, which will help us ignore the cross-site request origin warning from the browser. It is the security provided by the browser when we request them from a different server.

npm install nodemon --save-dev

Now 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);
});

Now, we need to start a node server using the following command.

nodemon server

So, our node.js server is running at port: 4000.

Vue Node Tutorial Example

Step 8: Setup and connect the MongoDB database.

If you are a beginner in the MongoDB database, then please check out my below tutorial.

Related Post: NoSQL MongoDB Tutorial

I have already installed MongoDB on Mac. So I am starting the MongoDB server by the following command. You can also use any MongoDB database client. I am just using the terminal for this demo because it is a simple application.

mongod

Inside an api folder, create one file called DB.js, which is responsible for connecting 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, username and password are empty, but in the production database, you need to create one user and assign that user to the database.

Now, 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 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, which is 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, and in our case, it is the vue.js frontend.

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

So, our 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 is showing an error on the console statement inside the Visual Studio Code editor or other editors, then ignore it because it is warning provided by the eslint configuration. It is not an actual error, and you can able to 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');

Now, 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 are 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 need to 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 and fetch the data from the database and then assign that data to the posts array, and then we loop through that array and display the data in the tabular format.

MEVN CRUD Example

Step 13: Send edit and update request

Now, 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.

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

So, 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, and now, you can also delete the values from the MongoDB database.

So, finally, 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

Steps to use code

  1. Clone the repository.
  2. Go inside the vue project and type this command: npm install
  3. Go inside the api folder and type this command: npm install
  4. Now, start the mongodb server.
  5. Also, start the node server using this command: nodemon server
  6. Go to the vue root project and type this command: npm run serve
  7. Go to http://localhost:8080, and your project is running.

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.