How Can I Set up a MEVN Stack on Linode?

Linode Staff

I see that you have MERN and MEAN Stacks in the Marketplace. What would be the best way to set up a MEVN Stack?

1 Reply

So MEVN stands for MongoDB, Express.js, VueJS, Node.js. With that in mind, I'm writing this using MongoDB as the launching off point. To begin with, you can [deploy a Linode using the MongoDB One-Click-App in the Linode Marketplace).

With that deployed, it's a bit complicated to install the rest. Here's how I was able to pull it off based on this guide. Starting things off, I also had to install nvm and then vue-cli. I go into the steps I took below on my MongoDB server.

Installing NPM

First, you will want to install NPM on your Linode with the following command:

sudo apt-get update
sudo apt-get install build-essential libssl-dev
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

You can find the right command for installing NVM in their documentation

To add NVM to your bash profile, run these following commands:

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Next, you will need to source the Bash prfile with the following command:

source ~ / .profile

Installing Node and NPM through NVM

After this, I want to install the latest version of Node. To find that, I ran the following command:

nvm ls-remote | grep -i latest

That gave me version v12.18.4 so I then want to run the following:

nvm install v12.18.4

With that installed, I want to make sure NPM is also installed and updated.

npm i npm -g

Using NPM to install vue-cli

Now that we have that, we can install the V in the MEVN stack: vue-CLI.

npm install -g @vue/cli

You can verify the installation with the following command:

 vue --version

Create a Vue.js project

To create the project, run the following command:

 vue create mevnproject

You can choose the default option or whatever works best for you. Once it's complete you want to get into the project folder and start the server.

cd mevnproject

Once in that folder you will want to create a vue.config.js file with the following contents to allow you to access the service over the network.

module.exports = {
  //...
  configureWebpack: {
    devServer: {
      host: '$IPADDRESS'
    }
  }
};

In the above, $IPADDRESS should be your Linode's IP address. With that file created, you can run the following:

npm run serve

Now you will want to load the page in your browser with the format of http://$IPADDRESS:8080 to ensure everything is working properly.

Install Vue Dependencies

The following will install the axios, vue-router and vue-axios dependencies. vue-router is used for routing the vue.js application to its necessary components, and vue-axios is used for sending the network request to the server.

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

With those installed, you will want to edit your main.js file located in ~/mevnproject/src/main.js to make sure it looks like the following:

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')

Create the Vue components

With all of that done, you will want to cd into the ~/mevnproject/src/components folder and add the following files

HomeComponent.vue

<template>
 <div >
   <div >
     <div >
       <div >Home Component</div>

       <div >
         I'm the Home Component component.
       </div>
     </div>
   </div>
 </div>
 </template>
 <script>
 export default {
 }
 </script>

Now you will want to import HomeComponent.vue into the ~/mevnproject/src/App.vue file by adding the following to the bottom:

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

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

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

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>

Configure the vue-router

Now we will need to go back to ~/mevnproject/src and edit the main.js file so it looks like the following:

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

This will import the vue-router module then create an array of routes using name, path, and components as the properties. Then, we created a router object and passed the mode history to the routes array. Once the application boots, these routes will be given to the vue application.

Now we will want to edit the App.vue file again so it includes like the following:

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

This will create the navigation bar.

Create a form

To create a form, we can add the following in 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>

Create a Node.js backend

cd back into ~/mevnproject then create a new folder by running:

mkdir api
cd api

Once in that folder, initialize the package.json file by running the following command:

npm init -y

This will create a file called package.json. Now you can install the node.js dependencies with the following command:

npm install express body-parser cors mongoose --save

One additional dependency we need is Nodemon serve.

npm install nodemon --save-dev

Now in the API folder we can create some additional files: server.js, DB.js, post.model.js, post.route.js. In the server.js file enter the following:

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

With these files created, start up a node server with the following command:

nodemon server

Configure the MongoDB

Since MongoDB is already installed, we don't need to go through the process of installing it ourselves. To start configuring everything, edit DB.js with the following:

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

With that added, let's edit the server.js file to import the new information in.

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

Create Mongoose Schema

to create this, edit the post.model.js file with the following:

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

Create Routes for the Node.js application

Now it's time to edit the post.route.js file.

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;

With that made, we need to import that into the server.js file so it looks like the following:

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

Send a Network Request with Axios

Now it's time to go back to the ~/mevnproject/src/main.js file to import axios and vue-axios.

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, with the Vue development server, Node.js server, and MongoDB servers running, it's time to add some additional code to the ~/mevnproject/src/components/CreateComponent.vue file. It should look like this:

<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(){
    let uri = 'http://localhost:4000/posts/add';
    this.axios.post(uri, this.post).then(() => {
       this.$router.push({name: 'posts'});
    });
        console.log(this.post);
      }
    }
  }
</script>

Display the Backend Data

Edit the IndexComponent.js file so it looks as follows:

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

Send edit and update request

Now that the backend data is showing, it's time to go back and edit the EditComponent.Vue file. It should look as follows:

<template>
 <div>
  <h1>Edit Post</h1>
  <form @submit.prevent="updatePost">
   <div >
    <div >
     <div >
      <label>Post Title:</label>
      <input type=text v-model="post.title">
     </div>
    </div>
    </div>
    <div >
     <div >
      <div >
       <label>Post Body:</label>
       <textarea v-model="post.body" rows="5"></textarea>
      </div>
     </div>
    </div><br />
    <div >
     <button >Update</button>
    </div>
  </form>
 </div>
</template>

<script>
  export default {

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

Delete the data

Now it's time to edit the IndexComponent.vue file one last time.

<template>
 <div>
   <h1>Posts</h1>
    <div >
     <div ></div>
     <div >
      <router-link :to="{ name: 'create' }" >Create Post</router-link>
     </div>
    </div><br />

    <table >
      <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 }}" >Edit</router-link></td>
         <td><button @click.prevent="deletePost(post._id)">Delete</button></td>
        </tr>
      </tbody>
    </table>
 </div>
</template>

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

With that, your MEVN stack should be created and you should be good to go.

Reply

Please enter an answer
Tips:

You can mention users to notify them: @username

You can use Markdown to format your question. For more examples see the Markdown Cheatsheet.

> I’m a blockquote.

I’m a blockquote.

[I'm a link] (https://www.google.com)

I'm a link

**I am bold** I am bold

*I am italicized* I am italicized

Community Code of Conduct