The comprehensive tutorial on MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat Application
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.
The scenario is very simple, just the rooms and the chats for each room. The first page will show the list of the rooms. After the user enters the room and fills the username or nickname then the user enters the chats with other users.
.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
That where we start the tutorial. We will create the MEVN stack Chat application 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.
Close the running Vue.js 2 app first by press ctrl+c
then type this command for adding Express.js modules and its dependencies.
npm install --save express body-parser morgan body-parser serve-favicon
Next, create a new folder called bin
then add a file called www
on the root of the Vue.js project folder.
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 project folder.
touch app.js
Open and edit app.js
then add this lines of codes.
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 this 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](http://localhost:3000 "http://localhost:3000")
. When you change the address to [http://localhost:3000/api/room](http://localhost:3000/api/room "http://localhost:3000/api/room")
or [http://localhost:3000/api/chat](http://localhost:3000/api/chat "http://localhost:3000/api/chat")
you will see this page.
We need to access data from MongoDB. For that, we will install and configure Mongoose.js. On the terminal type this command after stopping the running Express server.
npm install --save mongoose bluebird
Open and edit app.js
then add this 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.
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 this 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 this 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);
Open and edit again routes/room.js
then replace all codes with this.
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.
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 Restful 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 that sent from RESTful 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}
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 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
}
]
})
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.
npm i bootstrap-vue bootstrap@4.0.0-beta.2
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
Now, open and edit src/components/RoomList.vue
then add this lines of codes.
<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.
Now, open and edit src/components/AddRoom.vue
then add this 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 RESTful API.
Now, open and edit src/components/JoinRoom.vue
then add this 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.
Now, open and edit src/components/JoinRoom.vue
then add this 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.
Previous steps show you a regular and non-realtime transaction chat application. Now, we will make it real-time by using Socket.io. 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 this 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 this 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 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>
To run this MEVN (Vue.js 2) Chat Application 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.
Thanks!
=============================
Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter
☞ MERN Stack Front To Back: Full Stack React, Redux & Node.js
☞ The Complete Node.js Developer Course (3rd Edition)
☞ Angular & NodeJS - The MEAN Stack Guide
☞ NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)
☞ Docker for Node.js Projects From a Docker Captain
☞ Intro To MySQL With Node.js - Learn To Use MySQL with Node!
☞ Node.js Absolute Beginners Guide - Learn Node From Scratch
☞ React Node FullStack - Social Network from Scratch to Deploy
☞ Selenium WebDriver - JavaScript nodeJS webdriver IO & more!
☞ Complete Next.js with React & Node - Beautiful Portfolio App
☞ Build a Blockchain & Cryptocurrency | Full-Stack Edition
#node-js #angular #vue-js #typescript #mongodb