How Can I Set up a MEVN Stack on Linode?
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.