DEV Community

Cover image for A Chat app I made using MEVN stack and socket.io
readwarn
readwarn

Posted on

A Chat app I made using MEVN stack and socket.io

After months of procrastination, I'm finally writing my first article as a developer
So I made a chat app with the MEVN (MongoDB, Express, Vue.js, Node.js) stack and socket.io. The app functionalities are inspired by discord app.

The app features include:

  • User can login with a username and password or using other social media accounts
  • Users are automatically added to a welcome channel
  • Users can create a channel
  • Users can search and join a channel
  • Users can leave a channel
  • Users can edit their profile and change their display picture

Features I intend to add later

  • Users can browse through and join public channels
  • Users can directly message members of a channel
  • A simple profile card of a user is displayed on hovering over his/her username
  • User can change the theme of the app.

This post will focus on how socket.io is used on the app. Here is the Link to the source code, and the live link.

However, to get a clear understanding of this post, I'll explain few key terms.

  1. The channel schema
const mongoose=require('mongoose');
const Schema=mongoose.Schema;
const channelSchema=new Schema({
    name:String,
    description:String,
    members:[
        {
            type:mongoose.Schema.Types.ObjectId,
            ref:"User"
        }
    ],
    messages:[{
        type:mongoose.Schema.Types.ObjectId,
        ref:"Message"
    }]
})

module.exports=mongoose.model('Channel',channelSchema);

Enter fullscreen mode Exit fullscreen mode

When a user sends message to a channel, the channel is updated by pushing the new message to the channel's messages array.

  1. Channel: This represents the channel being currently viewed by a user

Alt Text

  1. Channels: This represents the array of channels a user belongs to.

Alt Text

  1. Updated Channel: This is a channel object updated with latest messages sent by members. i.e. new messages are already pushed to its messages array properties.

SOCKET.IO

The socket.io was used to allow for real-time bidirectional data flow between the client and the server. i.e. the client can emit event, along with some data, while the server listens to this event and handles them accordingly. This means that data can be exchanged between the client and the server without having to refresh the page.

Socket.io on the server

The socket.io is installed as shown

npm install socket.io
Enter fullscreen mode Exit fullscreen mode

The socket io connection is then set up with express server as shown.

const express=require('express');
const socket = require('socket.io');
const app=express();
const server = app.listen(process.env.PORT || 3000,function(){
  console.log("running");
});

const io = socket(server,{
  cors: {
    origin: "https://calm-meadow-71961.herokuapp.com",
    methods: ["GET","PUT", "POST"],
  }
});
Enter fullscreen mode Exit fullscreen mode

On the server side, the socket listens to three kinds of event emitted from the client side.

  • RegisterAll: This event is emitted whenever a client connects to the socket.io connection. The user's channels array is sent along with the event. This event is handled by subscribing the client to each channel's ID in the channels array i.e. the client joins an array of channel's ID room.
socket.on('registerAll',channels=>{
      channels.forEach(channel => {
         socket.join(channel._id);
      });
});
Enter fullscreen mode Exit fullscreen mode
  • Register: This event is emitted when a user joins a new channel. The new channel object is sent along with the event. This event is handled by subscribing the client to the new channel's ID i.e. the client joins the new room.
 socket.on('register',channel=>{
       socket.join(channel._id);
})
Enter fullscreen mode Exit fullscreen mode
  • messageSent: This event is emitted along with updatedChannel object when,
    • Message is sent to a channel;
    • User leaves a channel;
    • User joins a channel;
    • User creates a new channel. This event is handled by emitting the messageRecieved event along with the updatedChannel object to all clients that belong to the channel's ID room.
 socket.on('messageSent',(channel)=>{
       socket.to(channel._id).emit('messageReceived',channel);
    })
Enter fullscreen mode Exit fullscreen mode

Note: The messageSent and the Register events are both emitted from the client when a user joins a channel or creates a new channel

Socket.io on the client

Socket.io is installed on the client side

npm install socket.io-client;
Enter fullscreen mode Exit fullscreen mode

It is then imported and initialized with the express server url

<script>
import io from 'socket.io-client';
data(){
  return {
   disconnected:false,
   channels:[],
   channel:{},
   socket:io("https://whispering-everglades42925.herokuapp.com"),
  }
}
<script/>
Enter fullscreen mode Exit fullscreen mode

On the client side, the client handles three events emitted from the server side.

  • connect: This is a reserved event emitted when a client connects to the socket.io connection. The event is handled by emitting the 'regsiterAll' event along with the user's channels [the array of channels the user belongs to] and also setting the disconnected variable to false.
this.socket.on("connect", () => {
     if(this.channels.length>0){
          this.registerChannels(this.channels);
          this.disconnected=false;
     }
});
Enter fullscreen mode Exit fullscreen mode
  • disconnect: This is a reserved event emitted when a client is disconnected from the socket.io connection. This event is handled by setting the disconnected variable to true.
this.socket.on("disconnect", (reason) => {
      this.disconnected=true;
});
Enter fullscreen mode Exit fullscreen mode

Hence the paragraph, "disconnected", is displayed whenever the client is disconnected from the socket connection
<p class="disconnected" v-if="disconnected">disconnected</p>

  • messageRecieved: This event is handled by replacing a channel in the array of channels with the updated channel sent with the event.
 this.socket.on('messageReceived',(channel)=>{
       this.updateChannel(channel);
});`

Enter fullscreen mode Exit fullscreen mode

The updateChannel method is defined as shown below.

funtion updateChannel(updatedChannel){
             if(updatedChannel._id===this.channel._id){
                 this.channel=updatedChannel;
             }
             this.channels = this.channels.map(channel => (channel._id === updatedChannel._id) ? updatedChannel : channel)
        }
Enter fullscreen mode Exit fullscreen mode

The function takes the updatedChannel argument passed with the messageReceived event, sets the currently viewed channel
to the updated channel if it is the same as the updatedChannel, then replace the outdated channel in the user's channels with the updatedChannel

There goes my first post, Thanks for reading.

Top comments (0)