A Chat app I made using MEVN stack and socket.io
Tags: vue, node, mongodb, socket
Published: 2021-03-30 18:09:53

After months of procrastination, I'm finally writing my first article as a developer
So I made a [chat app](https://calm-meadow-71961.herokuapp.com/login) with the MEVN (MongoDB, Express, Vue.js, Node.js) stack and [socket.io](https://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](https://socket.io/) is used on the app. Here is the [Link](https://github.com/readwarn/Real-time-chat-app) to the source code, and the [live link](https://calm-meadow-71961.herokuapp.com/login).

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);

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


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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gbgavxjoj9ay6uqjfbbr.png)


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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hp1dqidaqjpaejyeb67.png)


4. **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
```
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"],
  }
});
```

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](https://socket.io/docs/v4/rooms/).

```
socket.on('registerAll',channels=>{
      channels.forEach(channel => {
         socket.join(channel._id);
      });
});
```

* **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);
})
```

* **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);
    })
```

>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;
``` 
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/>
```

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;
     }
});
```

* **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;
});
```
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);
});`

```
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)
        }
```

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.