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
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 axios, vue-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.
- HomeComponent.vue
- CreateComponent.vue
- EditComponent.vue
- 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.
- http://localhost:8080/create
- http://localhost:8080/posts
- http://localhost:8080/edit/21
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.
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.
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.
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 database. Mongoose 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); });
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.
- Vue development server
- Node.js server
- 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
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.
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.
Steps to use code
- Clone the repository.
- Go inside the vue project and type this command: npm install
- Go inside the api folder and type this command: npm install
- Now, start the mongodb server.
- Also, start the node server using this command: nodemon server
- Go to the vue root project and type this command: npm run serve
- Go to http://localhost:8080, and your project is running.
I’m trying it in server but it return error apis not working , i cloned the project
work..
best mevn crud tutorial that i’ve found so far. thanks
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);
Thank you this is very useful
Fixed the bug at the end on delete:
this.posts.splice(this.posts.findIndex(i => i._id == id), 1);
Clear and distinct. Good post. (y)
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 | }
Great tutorial bro. Thanks 🙂
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?