avatar
Build Chat App with MongoDB and Socket.io Javascript

> First and foremost, you need to have concept of Nodejs and MongoDB Atlas. Press the link here to make an Node.js application connect to Mongodb Atlas.

const express = require("./config/express.js"),
    mongoose = require("mongoose");

const app = express.init();
const server = require("http").createServer(app);

server.listen(3000, () => {
    console.log('Server started on port 3000');
});

/** Connect MongoDB from Node.js application */
mongoose.set('strictQuery', false);
mongoose.connect('mongodb+srv://flagtick:[email protected]/chatbox?retryWrites=true&w=majority', {
    useNewUrlParser: true, 
    useUnifiedTopology: true 
});

const connection = mongoose.connection;
connection.once("open", () => {

    const messageCollectionStream = connection.collection("messages").watch();
    messageCollectionStream.on("change", (change) => {
        switch (change.operationType) {
          case "insert":
            console.log(change.operationType);
            break;
          case "delete":
            break;
        }
    });
});

> Install socket.io from npm command.

npm i socket.io

> Configure server.js after installing socket.io library.

const express = require("./config/express.js"),
    mongoose = require("mongoose");

const app = express.init();
const server = require("http").createServer(app);
const io = require("socket.io")(server);

server.listen(3000, () => {
    console.log('Server started on port 3000');
});

/** Socket.io */
io.of("/api/socket").on("connection", (socket) => {
    console.log("socket.io: Connection established successfully: ", socket.id);
    socket.on("disconnect", () => {
      console.log("socket.io: Connection lose!: ", socket.id);
    });
});

/** Connect MongoDB from Node.js application */
mongoose.set('strictQuery', false);
mongoose.connect('mongodb+srv://flagtick:[email protected]/chatbox?retryWrites=true&w=majority', {
    useNewUrlParser: true, 
    useUnifiedTopology: true 
});
const connection = mongoose.connection;
connection.once("open", () => {
    const messageCollectionStream = connection.collection("messages").watch();
    messageCollectionStream.on("change", (change) => {
        switch (change.operationType) {
          case "insert":
            console.log(change.operationType);
            break;
          case "delete":
            break;
        }
    });
});

> To test if socket is opening and listening. We will use socket.io-client to test. Hence, let seek to write an client Node.js application. Using template from link here and remove server.js and node_modules.

npm install -g @vue/cli
vue create socketio-vue

Note: After build successfully, your folder structure looks like this:

> The next step, open root project and run the following command to install socket.io-client library.

npm install socket.io-client

Note: This would install the latest socket.io-client library in our Vue app.

> Let us start by creating .env file.

> type nul > .env

Add VUE_APP_SOCKET_ENDPOINT into .env file.

VUE_APP_SOCKET_ENDPOINT=http://localhost:3000/api/socket

Note: process.env.VUE_APP_SOCKET_ENDPOINT has value if you use Vue 3 and need to follow up format VUE_APP_<Your custom key>. If you update wrong format and value is undefined. In the other hands, if you need take a look how server start with endpoint? Here is '/api/socket' as below. 

io.of("/api/socket").on("connection", (socket) => {
    console.log("socket.io: Connection established successfully: ", socket.id);
  
    socket.on("disconnect", () => {
      console.log("socket.io: Connection lose!: ", socket.id);
    });
});

Hence, you need to configure properly endpoint in VUE_APP_SOCKET_ENDPOINT.

> Creating a socket client service by new folder named services and add custom file named socketio.service.js.

import { io } from 'socket.io-client';

class SocketioService {
    socket;
    constructor() {}
  
    setupSocketConnection() {
      this.socket = io(process.env.VUE_APP_SOCKET_ENDPOINT);
    }
}

export default new SocketioService();

> In App.vue we will use created() to create hook for setup socket client.

import { io } from 'socket.io-client';

class SocketioService {
    socket;
    constructor() {}
  
    setupSocketConnection() {
      this.socket = io(process.env.VUE_APP_SOCKET_ENDPOINT, { transports : ['websocket'] });
    }
}

export default new SocketioService();

> To run Vue application, you can follow up the command:

npm run-script build
npm run serve

Note: Vue client application starts at port 8080 as below:

Compiled successfully in 140ms                                                                                                                                 12:54:25 AM
  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.1.40:8080/

> Look at console in server where we start at port 3000. Here is batchmode to track how client request.

Server started on port 3000
socket.io: Connection established successfully:  VWKOKlb6RPYR5VoQAABt
socket.io: Connection established successfully:  bURyEpQHMLwHQTGCAABv
socket.io: Connection established successfully:  oWBTQzoLjZoHdW8LAABy
socket.io: Connection established successfully:  3qNMkdCjWzIOSdipAAB1
socket.io: Connection lose!:  bURyEpQHMLwHQTGCAABv
socket.io: Connection lose!:  3qNMkdCjWzIOSdipAAB1
socket.io: Connection established successfully:  iL2N_24Ep4jNaKwYAAB6
socket.io: Connection established successfully:  JN1QTbnHpQ4sJB6JAAC0
socket.io: Connection established successfully:  DLpeEvX_yOiRmmoKAAC2
socket.io: Connection established successfully:  Ux-pXVE1a4jusSlOAAC4
socket.io: Connection established successfully:  ZwN9mdVDUQJ8yCP6AAC6

> Handle disconnection from client side if we unmount Vue component.

  created() {
    SocketioService.setupSocketConnection();
  },
  beforeUnmount() {
    SocketioService.disconnect();
  }

And add function disconnect() in socketio.service.js file.

disconnect() {
    if (this.socket) {
        this.socket.disconnect();
    }
}

> Now, let talk about listening from server at port 3000. How? we will define channel or room or whatever to determine incoming request and merge inside it.

/** Socket.io from server */
io.of("/api/socket").on("connection", (socket) => {
    console.log("socket.io: Connection established successfully: ", socket.id);
  
    socket.on("disconnect", () => {
      console.log("socket.io: Connection lose!: ", socket.id);
    });


    socket.on('technical-issue', (msg) => {
      console.log('message: ' + msg);
    });
});

Note: Our channel or room is technical-issue. That is key for using emit() method to trigger from client.

> Build Chatbox UI component by replacing HelloWorld component. Look at here as below

<template>
  <div class="chat">
   <div class="message-box">
      <div class="wp-block-search__inside-wrapper " style="width: 50%">
        <input type="search" id="wp-block-search__input-4" v-model="msg" class="wp-block-search__input wp-block-search__input" name="s" placeholder="" required="">
        <button type="submit" @click.prevent="addMessage" class="wp-block-search__button has-icon wp-element-button" aria-label="Search">
          <svg class="search-icon" viewBox="0 0 24 24" width="24" height="24">
              <path d="M13.5 6C10.5 6 8 8.5 8 11.5c0 1.1.3 2.1.9 3l-3.4 3 1 1.1 3.4-2.9c1 .9 2.2 1.4 3.6 1.4 3 0 5.5-2.5 5.5-5.5C19 8.5 16.5 6 13.5 6zm0 9.5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"></path>
          </svg>
        </button>
      </div>
   </div>
  
</div>
</template>
<script>
import SocketioService from '../services/socketio.service.js';
export default {
  name: 'HelloWorld',
  data() {
    return {
      msg: ''
    }
  },
  methods: {
    addMessage() {
      SocketioService.sendMessage('technical-issue', this.msg);
    }
  }
}
</script>
<style scoped>
.wp-block-search__inside-wrapper {
  margin: auto;
}
.wp-block-search__inside-wrapper {
    display: flex;
    flex: auto;
    flex-wrap: nowrap;
    max-width: 100%;
}
#wp-block-search__input-4 {
  width: 100%;
}
</style>

Note: In socketio.service.js file, we will define method to send message comes along with channel.

class SocketioService {
    socket;
    constructor() {}
  
    setupSocketConnection() {
      this.socket = io(process.env.VUE_APP_SOCKET_ENDPOINT, { transports : ['websocket'] });
    }

    sendMessage(channel, message) {
      this.socket.emit(channel, message);
    }
}

Here is batch mode from server:

&gt; pg@1.0.0 start C:\Users\admin\Documents\nodejs
> node server.js

Server started on port 3000
socket.io: Connection established successfully:&nbsp; s92vWB4Pjnzm8WovAAAB
socket.io: Connection lose!:&nbsp; s92vWB4Pjnzm8WovAAAB
socket.io: Connection established successfully:&nbsp; BN9jS1NA0EsrFPFqAAAD
socket.io: Connection lose!:&nbsp; BN9jS1NA0EsrFPFqAAAD
socket.io: Connection established successfully:&nbsp; u8-YPEZY1sG5ocl8AAAF
message: Hello World
socket.io: Connection lose!:&nbsp; u8-YPEZY1sG5ocl8AAAF
socket.io: Connection established successfully:&nbsp; F14GjJYVep_p4q_dAAAH
message: Hello World
socket.io: Connection lose!:&nbsp; F14GjJYVep_p4q_dAAAH
socket.io: Connection established successfully:&nbsp; BUx3PjcpSxGd9KZ8AAAJ

> So, we already resolve connection from client to server. Now, let talk about how server response to client? We will use watch() method in mongodb to trigger event for client to update.

> Be assumption that client initialize a request to server and update one message to mongodb. Our server will based on changes from mongodb generate channel and emit() to notify client.

<template>
  <div class="chat">
   <div class="message-box">
      <div class="wp-block-search__inside-wrapper " style="width: 50%">
        <input type="search" id="wp-block-search__input-4" v-model="msg" class="wp-block-search__input wp-block-search__input" name="s" placeholder="" required="">
        <button type="submit" @click.prevent="addMessage" class="wp-block-search__button has-icon wp-element-button" aria-label="Search">
          <svg class="search-icon" viewBox="0 0 24 24" width="24" height="24">
              <path d="M13.5 6C10.5 6 8 8.5 8 11.5c0 1.1.3 2.1.9 3l-3.4 3 1 1.1 3.4-2.9c1 .9 2.2 1.4 3.6 1.4 3 0 5.5-2.5 5.5-5.5C19 8.5 16.5 6 13.5 6zm0 9.5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"></path>
          </svg>
        </button>
      </div>
   </div>
</div>
</template>
<script>
window.axios = require('axios');
import SocketioService from '../services/socketio.service.js';
export default {
  name: 'HelloWorld',
  data() {
    return {
      msg: ''
    }
  },
  methods: {
    async addMessage() {
      const response = await window.axios.post(`${process.env.VUE_APP_ROOT_ENDPOINT}/api/message/addMessage`,
        {
          message: this.msg,
          date_created: new Date().toISOString(),
        }
      );
      if (response.data.success) {
        SocketioService.setupChannelClient('technical-issue');
      } else {
        console.log('Failure');
      }
    }
  }
}
</script>
<style scoped>
.wp-block-search__inside-wrapper {
  margin: auto;
}
.wp-block-search__inside-wrapper {
    display: flex;
    flex: auto;
    flex-wrap: nowrap;
    max-width: 100%;
}
#wp-block-search__input-4 {
  width: 100%;
}
</style>

Note: As you can see here, we had use REST API to call server instead of using Socket client. That trigger mongodb update data and trigger function watch() from server. In meantime, we will setup a listener to listen changes on which channel/topic/room as created from client.

Why we declare SocketioService.setupChannelClient('technical-issue'); in here? Because, we know exactly server will listen from watch event triggered by mongodb. Hence, we will define to get emit() - callback() from server.

import { io } from 'socket.io-client';
class SocketioService {
    socket;
    constructor() {}
  
    setupSocketConnection() {
      this.socket = io(process.env.VUE_APP_SOCKET_ENDPOINT, { transports : ['websocket'] });
    }

    sendMessage(channel, message) {
      this.socket.emit(channel, message);
    }

    async setupChannelClient(channel) {
      let res = await new Promise((resolve) => {
        this.socket.on(channel, (data) => {
          resolve(data);
        });
      });
      console.log(res);
    }
}
export default new SocketioService();

> Move to server (that run at port 3000) and check console as below:

socket.io: Connection established successfully:  Nw0Y92lEXrKYS5tVAAAv
message: Now
POST /api/message/addMessage 200 46.282 ms - 56
insert

Note: That means server is listening and incoming message is Now, REST APIs call is /api/message/addMessage and watch() return insert event.

const express = require("./config/express.js"),
    mongoose = require("mongoose");

const app = express.init();
const server = require("http").createServer(app);
const io = require("socket.io")(server);

server.listen(3000, () => {
    console.log('Server started on port 3000');
});
/** Socket.io */
io.of("/api/socket").on("connection", (socket) => {
    console.log("socket.io: Connection established successfully: ", socket.id);
    socket.on("disconnect", () => {
      console.log("socket.io: Connection lose!: ", socket.id);
    });
    // socket.on('technical-issue', (msg) => {
    //   console.log('message: ' + msg);
    // });
});


/** Connect MongoDB from Node.js application */
mongoose.set('strictQuery', false);
mongoose.connect('mongodb+srv://flagtick:[email protected]/chatbox?retryWrites=true&w=majority', {
    useNewUrlParser: true, 
    useUnifiedTopology: true 
});
const connection = mongoose.connection;
connection.once("open", () => {
    const messageCollectionStream = connection.collection("messages").watch();
    messageCollectionStream.on("change", (change) => {
        switch (change.operationType) {
          case "insert":
            console.log(change.operationType);
            io.of("/api/socket").emit("technical-issue", 'Your problem will be solving. Please wait for a moment!');
            break;
          case "delete":
            break;
        }
    });
});

> Now, let open Browser in client side and check console window.

> You can get source code for server in here and client in here. Feel free to visit my website.

You need to login to do this manipulation!