MEVN Tutorial: The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application. Previously we have a tutorial on build chat application using MEAN Stack, now we build this chat application using MEVN (MongoDB, Express.js, Vue.js 2, Node.js) Stack. The different just now we use Vue.js 2 and Axios, we keep using MongoDB, Node.js, Express, and Socket.io.

Table of Contents:

The scenario for this MEVN chat web app is very simple, just the rooms and the chats for each room. The first page will show the list of rooms. After the user enters the room and fills the username or nickname then the user enters the chats with other users. All activity or data fetch are realtime using Socket.io.

The following tools, frameworks, and modules are required for this tutorial:



We assume that you have already installed Node.js and able to run Node.js command line (Windows) or `npm` on the terminal (MAC/Linux). Open the terminal or Node command line then type this command to install `vue-cli`.

sudo npm install -g vue-cli

Vue CLI is fully configurable without the need for ejecting. This allows your project to stay up-to-date for the long run. That where we start the tutorial. We will create the MEVN stack Chat application using `vue-cli`.



Create Vue.js App using Vue-CLI

To create a new Vue.js 2 application using `vue-cli` simply type this command from terminal or Node command line.

vue init webpack mevn-chat

There will be a lot of questions, just leave it as default by always pressing enter key. Next, go to the newly created Vue.js project folder then install all default required modules by type this command.

cd ./mevn-chat

Now, check the Vue.js 2 application by running the application using this command.

npm run dev

Open your browser then go to `localhost:8080` and you should see this page when everything still on the track.



Install Express.js as REST API Server

We will be using Express.js as REST API server because with a myriad of HTTP utility methods and middleware at your disposal, creating a robust API is quick and easy. Close the running Vue.js 2 app first by press `ctrl+c` then type this command for adding Express.js modules and its dependencies (body-parser, morgan-serve-favicon).

npm install --save express body-parser morgan serve-favicon

Next, create a new folder called `bin` then add a file called `www` on the root of the Vue.js project folder. You may choose your own folder or file for starting Node.js application or server.

mkdir bin touch bin/www

Open and edit `www` file then add these lines of codes that contains configuration for an HTTP server, PORT, and error handling.

#!/usr/bin/env node /** * Module dependencies. */ var app = require('../app'); var debug = require('debug')('mean-app:server'); var http = require('http'); /** * Get port from environment and store in Express. */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ var server = http.createServer(app); /** * Listen on provided port, on all network interfaces. */ server.listen(port); server.on('error', onError); server.on('listening', onListening); /** * Normalize a port into a number, string, or false. */ function normalizePort(val) { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } /** * Event listener for HTTP server "error" event. */ function onError(error) { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': console.error(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': console.error(bind + ' is already in use'); process.exit(1); break; default: throw error; } } /** * Event listener for HTTP server "listening" event. */ function onListening() { var addr = server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; debug('Listening on ' + bind); }

Next, change the default server what run by `npm` command. Open and edit `package.json` then replace `start` value inside `scripts`.

"scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run build && node ./bin/www", "unit": "jest --config test/unit/jest.conf.js --coverage", "e2e": "node test/e2e/runner.js", "test": "npm run unit && npm run e2e", "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", "build": "node build/build.js" },

Next, create `app.js` in the root of the project folder.

touch app.js

Open and edit `app.js` then add these lines of codes that same as Express.js generated the application.

var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var bodyParser = require('body-parser'); var room = require('./routes/room'); var chat = require('./routes/chat'); var app = express(); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({'extended':'false'})); app.use(express.static(path.join(__dirname, 'dist'))); app.use('/rooms', express.static(path.join(__dirname, 'dist'))); app.use('/api/room', room); app.use('/api/chat', chat); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app;

Next, create routes folder then create routes file for the room and chat.

mkdir routes touch routes/room.js touch routes/chat.js

Open and edit `routes/room.js` file then add these lines of codes.

var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.send('Express RESTful API'); }); module.exports = router;

Do the same way with `routes/chat.js`. Now, run the server using this command.

npm start

You will see the previous Vue.js landing page when you point your browser to `http://localhost:3000`. When you change the address to `http://localhost:3000/api/room` or `http://localhost:3000/api/chat` you will see this page.



Install and Configure Mongoose.js

We need to access data from MongoDB. For that, we will install and configure Mongoose.js because Mongoose provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box. On the terminal type this command to install Mongoose and bluebird modules after stopping the running Express server.

npm install --save mongoose bluebird

Open and edit `app.js` then add these lines after another variable line.

var mongoose = require('mongoose'); mongoose.Promise = require('bluebird'); mongoose.connect('mongodb://localhost/mevn-chat', { promiseLibrary: require('bluebird') }) .then(() => console.log('connection succesful')) .catch((err) => console.error(err));

Now, run MongoDB server on different terminal tab or command line or run from the service.

mongod

Next, you can test the connection to MongoDB run again the Node application and you will see this message on the terminal.

connection succesful

If you are still using built-in Mongoose Promise library, you will get this deprecated warning on the terminal.

(node:42758) DeprecationWarning: Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

That's the reason why we added `bluebird` modules and register it as Mongoose Promise library.



Create Mongoose.js Model or Schema

Add a models folder on the root of project folder for hold Mongoose.js model files then add Javascript file for Room and Chat.

mkdir models touch models/Room.js touch models/Chat.js

Next, open and edit `models/Room.js` then add these lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema; var RoomSchema = new mongoose.Schema({ room_name: String, created_date: { type: Date, default: Date.now }, }); module.exports = mongoose.model('Room', RoomSchema);

Open and edit `models/Chat.js` then add these lines of codes.

var mongoose = require('mongoose'), Schema = mongoose.Schema; var ChatSchema = new mongoose.Schema({ room : { type: Schema.Types.ObjectId, ref: 'Room' }, nickname: String, message: String, created_date: { type: Date, default: Date.now }, }); module.exports = mongoose.model('Chat', ChatSchema);



Create Express Routes

Express routes refer to how an application’s endpoints (URIs) respond to client requests. Open and edit again `routes/room.js` then replace all codes with this CRUD operation that populate Room schema.

var express = require('express'); var router = express.Router(); var mongoose = require('mongoose'); var Room = require('../models/Room.js'); /* GET ALL ROOMS */ router.get('/', function(req, res, next) { Room.find(function (err, products) { if (err) return next(err); res.json(products); }); }); /* GET SINGLE ROOM BY ID */ router.get('/:id', function(req, res, next) { Room.findById(req.params.id, function (err, post) { if (err) return next(err); res.json(post); }); }); /* SAVE ROOM */ router.post('/', function(req, res, next) { Room.create(req.body, function (err, post) { if (err) return next(err); res.json(post); }); }); /* UPDATE ROOM */ router.put('/:id', function(req, res, next) { Room.findByIdAndUpdate(req.params.id, req.body, function (err, post) { if (err) return next(err); res.json(post); }); }); /* DELETE ROOM */ router.delete('/:id', function(req, res, next) { Room.findByIdAndRemove(req.params.id, req.body, function (err, post) { if (err) return next(err); res.json(post); }); }); module.exports = router;

Open and edit again `routes/chat.js` then replace all codes with this CRUD operation that populate Chat schema.

var express = require('express'); var router = express.Router(); var mongoose = require('mongoose'); var Chat = require('../models/Chat.js'); /* GET ALL CHATS */ router.get('/', function(req, res, next) { Chat.find(function (err, products) { if (err) return next(err); res.json(products); }); }); /* GET SINGLE CHAT BY ID */ router.get('/:id', function(req, res, next) { Chat.findById(req.params.id, function (err, post) { if (err) return next(err); res.json(post); }); }); /* SAVE CHAT */ router.post('/', function(req, res, next) { Chat.create(req.body, function (err, post) { if (err) return next(err); res.json(post); }); }); /* UPDATE CHAT */ router.put('/:id', function(req, res, next) { Chat.findByIdAndUpdate(req.params.id, req.body, function (err, post) { if (err) return next(err); res.json(post); }); }); /* DELETE CHAT */ router.delete('/:id', function(req, res, next) { Chat.findByIdAndRemove(req.params.id, req.body, function (err, post) { if (err) return next(err); res.json(post); }); }); module.exports = router;

Run again the Express server then open the other terminal or command line to test the REST API by type this command.

curl -i -H "Accept: application/json" localhost:3000/api/room

If that command return response like below then REST API is ready to go.

HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 2 ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w" Date: Sun, 05 Aug 2018 13:11:30 GMT Connection: keep-alive []

Now, let's populate Room collection with initial data sent from REST API. Run this command to populate it.

curl -i -X POST -H "Content-Type: application/json" -d '{ "room_name":"Javascript" }' localhost:3000/api/room

You will see this response to the terminal if success.

HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 109 ETag: W/"6d-OGpcih/JWvJGrYAhMP+KBYQOvNQ" Date: Sun, 05 Aug 2018 13:35:50 GMT Connection: keep-alive {"_id":"5b66fd3581b9291558dc90b7","room_name":"Javascript","created_date":"2018-08-05T13:35:49.803Z","__v":0}



Create Vue.js 2 Component and Routing

We will use Vue-Router to make routes for Vue components navigation. Vue Router is the official router for Vue.js. It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze. Now, it's time for Vue.js 2 or front end part. First, create or add the component of the room list, add a room, join a room, chat room. Create all of those files into the components folder.

touch src/components/RoomList.vue touch src/components/AddRoom.vue touch src/components/JoinRoom.vue touch src/components/ChatRoom.vue

Now, open and edit `src/router/index.js` then add the import for all the above new components.

import Vue from 'vue' import Router from 'vue-router' import RoomList from '@/components/RoomList' import AddRoom from '@/components/AddRoom' import JoinRoom from '@/components/JoinRoom' import ChatRoom from '@/components/ChatRoom'

Add the router to each component or page.

export default new Router({ routes: [ { path: '/', name: 'RoomList', component: RoomList }, { path: '/add-room', name: 'AddRoom', component: AddRoom }, { path: '/join-room/:id', name: 'JoinRoom', component: JoinRoom }, { path: '/chat-room/:id/:nickname', name: 'ChatRoom', component: ChatRoom } ] })



Add Axios and Bootstrap-Vue

For UI or styling, we are using Bootstrap Vue. BootstrapVue use to build responsive, mobile-first projects on the web using Vue.js and the world's most popular front-end CSS library Bootstrap v4. To install Bootstrap-Vue type this command on the terminal.

Open and edit `src/main.js` then add the imports for Bootstrap-Vue.

import Vue from 'vue' import BootstrapVue from 'bootstrap-vue' import App from './App' import router from './router' import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap-vue/dist/bootstrap-vue.css'

Add this line after `Vue.config`.

Vue.use(BootstrapVue)

Next, we are using Axio for accessing REST API provided by Express.js. Axios is a promise-based HTTP client for the browser and node.js. To install it, in the terminal type this command.

npm install axios --save



Modify Vue.js 2 Component for Room List

Now, open and edit `src/components/RoomList.vue` then add these lines of codes that contain Bootstrap-Vue templates and data fetch for Chat Room List.

<template> <b-row> <b-col cols="12"> <h2> Room List <b-link href="#/add-room">(Add Room)</b-link> </h2> <b-table striped hover :items="rooms" :fields="fields"> <template slot="actions" scope="row"> <b-btn size="sm" @click.stop="join(row._id)">Join</b-btn> </template> </b-table> <ul v-if="errors && errors.length"> <li v-for="error of errors"> {{error.message}} </li> </ul> </b-col> </b-row> </template> <script> import axios from 'axios' export default { name: 'BookList', data () { return { fields: { room_name: { label: 'Room Name', sortable: true, 'class': 'text-center' }, created_date: { label: 'Created Date', sortable: true }, actions: { label: 'Action', 'class': 'text-center' } }, rooms: [], errors: [] } }, created () { axios.get(`http://localhost:3000/api/room`) .then(response => { this.rooms = response.data }) .catch(e => { this.errors.push(e) }) }, methods: { join (id) { this.$router.push({ name: 'JoinRoom', params: { id: id } }) } } } </script>

There are template and script in one file. The template block contains HTML tags. Script block contains variables, page lifecycle and methods or functions.



Modify Vue.js 2 Component for Add Room

Now, open and edit `src/components/AddRoom.vue` then add these lines of codes.

<template> <b-row> <b-col align-self="start"> </b-col> <b-col cols="6" align-self="center"> <h2> Add Room <b-link href="#/">(Room List)</b-link> </h2> <b-form @submit="onSubmit"> <b-form-group id="fieldsetHorizontal" horizontal :label-cols="4" breakpoint="md" label="Enter Room Name"> <b-form-input id="room_name" :state="state" v-model.trim="room.room_name"></b-form-input> </b-form-group> <b-button type="submit" variant="primary">Save</b-button> </b-form> </b-col> <b-col align-self="end"> </b-col> </b-row> </template> <script> import axios from 'axios' export default { name: 'AddRoom', data () { return { room: {} } }, methods: { onSubmit (evt) { evt.preventDefault() axios.post(`http://localhost:3000/api/room`, this.room) .then(response => { this.$router.push({ name: 'RoomList' }) }) .catch(e => { this.errors.push(e) }) } } } </script>

That code contains the template for room form, the script that contains Vue.js 2 codes for hold room model and methods for saving room to REST API.



Modify Vue.js 2 Component for Join Room

Now, open and edit `src/components/JoinRoom.vue` then add these lines of codes.

<template> <b-row> <b-col cols="6"> <h2> Join Room <b-link href="#/">(Room List)</b-link> </h2> <b-form @submit="onSubmit"> <b-form-group id="fieldsetHorizontal" horizontal :label-cols="4" breakpoint="md" label="Enter Nickname"> <b-form-input id="nickname" :state="state" v-model.trim="chat.nickname"></b-form-input> </b-form-group> <b-button type="submit" variant="primary">Join</b-button> </b-form> </b-col> </b-row> </template> <script> import axios from 'axios' export default { name: 'JoinRoom', data () { return { chat: {} } }, methods: { onSubmit (evt) { evt.preventDefault() this.chat.room = this.$route.params.id this.chat.message = this.chat.nickname + ' join the room' axios.post(`http://localhost:3000/api/chat`, this.chat) .then(response => { this.$router.push({ name: 'ChatRoom', params: { id: this.$route.params.id, nickname: response.data.nickname } }) }) .catch(e => { this.errors.push(e) }) } } } </script>

That code contains the template for join room form, the script that contains Vue.js 2 codes for hold chat model and methods for saving room to RESTful API.



Modify Vue.js 2 Component for Chat Room

Now, open and edit `src/components/JoinRoom.vue` then add these lines of codes.

<template> <b-row> <b-col cols="12"> <h2> Chat Room </h2> <b-list-group class="panel-body"> <b-list-group-item v-for="(item, index) in chats" class="chat"> <div class="left clearfix" v-if="item.nickname === nickname"> <b-img left src="http://placehold.it/50/55C1E7/fff&text=ME" rounded="circle" width="75" height="75" alt="img" class="m-1" /> <div class="chat-body clearfix"> <div class="header"> <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted"> <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small> </div> <p>{{ item.message }}</p> </div> </div> <div class="right clearfix" v-else> <b-img right src="http://placehold.it/50/55C1E7/fff&text=U" rounded="circle" width="75" height="75" alt="img" class="m-1" /> <div class="chat-body clearfix"> <div class="header"> <strong class="primary-font">{{ item.nickname }}</strong> <small class="pull-right text-muted"> <span class="glyphicon glyphicon-time"></span>{{ item.created_date }}</small> </div> <p>{{ item.message }}</p> </div> </div> </b-list-group-item> </b-list-group> <ul v-if="errors && errors.length"> <li v-for="error of errors"> {{error.message}} </li> </ul> <b-form @submit="onSubmit" class="chat-form"> <b-input-group prepend="Message"> <b-form-input id="message" :state="state" v-model.trim="chat.message"></b-form-input> <b-input-group-append> <b-btn type="submit" variant="info">Send</b-btn> </b-input-group-append> </b-input-group> </b-form> </b-col> </b-row> </template> <script> import axios from 'axios' export default { name: 'ChatRoom', data () { return { chats: [], errors: [], nickname: this.$route.params.nickname, chat: {} } }, created () { axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id) .then(response => { this.chats = response.data }) .catch(e => { this.errors.push(e) }) }, methods: { logout (id) { this.$router.push({ name: 'JoinRoom', params: { id: id } }) }, onSubmit (evt) { evt.preventDefault() this.chat.room = this.$route.params.id this.chat.nickname = this.$route.params.nickname axios.post(`http://localhost:3000/api/chat`, this.chat) .then(response => { // this.$router.push({ // name: 'ChatRoom', // params: { id: this.$route.params.id, nickname: response.data.nickname } // }) }) .catch(e => { this.errors.push(e) }) } } } </script> <style> .chat .left .chat-body { text-align: left; margin-left: 100px; } .chat .right .chat-body { text-align: right; margin-right: 100px; } .chat .chat-body p { margin: 0; color: #777777; } .panel-body { overflow-y: scroll; height: 350px; } .chat-form { margin: 20px auto; width: 80%; } </style>

That code contains the template of the main chat application consist of chat list and sends message form.



Integrate Socket.io Realtime Chat App

Previous steps show you a regular and non-realtime transaction chat application. Now, we will make it real-time by using Socket.io. Socket.IO enables real-time, bidirectional and event-based communication. It works on every platform, browser or device, focusing equally on reliability and speed. First, install `socket.io` module by type this command.

npm install --save socketio socket.io-client

Next, open and edit `routes/chat.js` then declare the Socket IO and `http` module.

var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server);

Add these lines of codes for Socket IO functions.

server.listen(4000); // socket io io.on('connection', function (socket) { console.log('User connected'); socket.on('disconnect', function() { console.log('User disconnected'); }); socket.on('save-message', function (data) { console.log(data); io.emit('new-message', { message: data }); }); });

In that code, we are running Socket.io to listen for 'save-message' that emitted from the client and emit 'new-message' to the clients. Next, open and edit `src/components/JoinRoom.vue` then add this import.

import * as io from 'socket.io-client'

Declare Socket IO variable.

data () { return { chat: {}, socket: io('http://localhost:4000') } },

Add Socket IO `emit` function after successful join room.

axios.post(`http://localhost:3000/api/chat`, this.chat) .then(response => { this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: 'Join this room', created_date: new Date() }); this.$router.push({ name: 'ChatRoom', params: { id: this.$route.params.id, nickname: response.data.nickname } }) }) .catch(e => { this.errors.push(e) })

Next, open and edit `src/components/ChatRoom.vue` then add these imports and use as Vue module.

import Vue from 'vue' import * as io from 'socket.io-client' import VueChatScroll from 'vue-chat-scroll' Vue.use(VueChatScroll)

Declare Socket IO variable.

data () { return { chats: [], errors: [], nickname: this.$route.params.nickname, chat: {}, socket: io('http://localhost:4000') } },

Add this Socket IO `on` function to `created` method.

created () { axios.get(`http://localhost:3000/api/chat/` + this.$route.params.id) .then(response => { this.chats = response.data }) .catch(e => { this.errors.push(e) }) this.socket.on('new-message', function (data) { if(data.message.room === this.$route.params.id) { this.chats.push(data.message) } }.bind(this)) },

Add Logout function inside methods and add Socket IO `emit` method in the POST response.

methods: { logout () { this.socket.emit('save-message', { room: this.chat.room, nickname: this.chat.nickname, message: this.chat.nickname + ' left this room', created_date: new Date() }); this.$router.push({ name: 'RoomList' }) }, onSubmit (evt) { evt.preventDefault() this.chat.room = this.$route.params.id this.chat.nickname = this.$route.params.nickname axios.post(`http://localhost:3000/api/chat`, this.chat) .then(response => { this.socket.emit('save-message', response.data) this.chat.message = '' }) .catch(e => { this.errors.push(e) }) } }

Finally, to make Chat list always scroll to the bottom of the Chat element add install this module.

npm install --save vue-chat-scroll

That module already imported and declared above. Next, add to the Chat element.

<b-list-group class="panel-body" v-chat-scroll> ... </b-list-group>



Run and Test The MEVN (Vue.js 2) Chat Web App

To run this MEVN (Vue.js 2) Chat Web App locally, make sure MongoDB service is running. Type this command to build the Vue.js 2 application then run the Express.js application.

npm start

Next, open the different browser (ex: Chrome and Firefox) then go to the `localhost:3000` on both of the browsers. You will see this page and you can start Chat.





That it's, the MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App. You can find the full working source code on our GitHub.

That just the basic. If you need more deep learning about MEVN Stack, Vue.js or related you can take the following cheap course:

Thanks!