Private chat using Socket.io
Tags: dohackathon, webdev, programming
Published: 2021-01-04 16:23:03

Hey guys! I haven't been updating the series here but I have made quite a lot of progress on the Goodeed app. One of the features of this app includes a private chat and what better technology to use for real time communication than Socket.io.

The idea is that if the user wants to know more about the item they want to borrow or they're not comfortable leaving those details such as where and when to meet in the comments, they can choose to message the other privately. So, basically it is a 1-to-1 chat.

There are a few ways to go about creating socket connections and to make a private messaging feature work but after a few attempts, I've decided to do the *Rooms* route in which a chat will be held in a room where there can only be a maximum of 2 participants. Check out more about Socket.io rooms [here](https://socket.io/docs/v3/rooms/).

Socket.io requires both server and client integration to work. The flow would be to create socket instance on the server and upon connection, the socket will have access to [events](https://socket.io/docs/v3/emitting-events/) to either listen or receive them. The client does pretty much the same thing, only that the client will connect to the server's port. 

```javascript

const { Server } = require('socket.io');

const io = new Server(res.socket.server);

io.on('connection', socket => {
  console.log(socket.id + ' ==== connected');

  // creating a room name that's unique using both user's unique username

  socket.on('join', roomName => {
  let split = roomName.split('--with--'); // ['username2', 'username1']

  let unique = [...new Set(split)].sort((a, b) => (a < b ? -1 : 1)); // ['username1', 'username2']

  let updatedRoomName = `${unique[0]}--with--${unique[1]}`; // 'username1--with--username2'

   Array.from(socket.rooms)
        .filter(it => it !== socket.id)
        .forEach(id => {
          socket.leave(id);
          socket.removeAllListeners(`emitMessage`);
        });

   socket.join(updatedRoomName);

   socket.on(`emitMessage`, message => {
      Array.from(socket.rooms)
           .filter(it => it !== socket.id)
           .forEach(id => {
              socket.to(id).emit('onMessage', message);
           });
        });
      });

    socket.on('disconnect', () => {
      console.log(socket.id + ' ==== diconnected');
      socket.removeAllListeners();
     });
   });
```

So let's walkthrough this a little. I found that a single connection can indeed join multiple rooms which is not what I want. When the user clicks on a chat, we want to emit `join` from the client side with a string that has both participants' unique usernames like *username1--with--username2*. This shows that username1 and username2 has to join a unique room. Since there are two clients here, the `roomName` is sorted in a way that is `currentUser--with--whoTheUserWantsToChatWith`. So a `sort` needs to happen so we generate a unique room name that the two participants can join the same one.

Socket.io automatically creates a unique id for a connected client and that id itself is a room. For example, logging `socket.rooms` when a client (let's take `username1`) with a socket.id (`123123`) is connected will give us 

```javascript
Array.from(socket.rooms) // ['123123']
```

Before joining a private chat room with `username2`, first we need to make sure there is only 1 room which is the socket.id `123123` (like the example above), and no other rooms. If there are other rooms though, we need to leave that room with `socket.leave(roomName)` and remove `emitMessage` listener.

Then we'll join the client 

```javascript
socket.join(roomName)

console.log(Array.from(socket.rooms) // ['123123', 'username1--with--username2'])
```

Now it's time to emit a message from the server to the client. 

```javascript
socket.on(`emitMessage`, message => {
      Array.from(socket.rooms)
           .filter(it => it !== socket.id)
           .forEach(id => {
              socket.to(id).emit('onMessage', message);
           });
        });
      });
```

Here I'm listening an `emitMessage` event and a `message` parameter that client sent over. Now I only want to send the message IN the only room that client is in **but not its own socket.id**. Once a filter is done, there I am pointing to the unique room and emit an `onMessage` event that the client is listening with the `message`.

Also, not forgetting to disconnect the socket and removing all listeners once the socket is no longer in use.

On the client side, we're gonna set up event listeners that match the server's. In order to join a room, we would do something like this

```javascript
// user: username2, anotherUser: username1
socket.emit('join', `${user}--with--${anotherUser}`);
``` 

To send a message to the server, the client will need to emit `emitMessage` event with the message.

```javascript
// send message
 socket.emit(`emitMessage`, "Hello world!");
```

To receive message from the server that's being emitted from `onMessage` event:

```javascript
// receive message
socket.on('onMessage', msg => {
console.log(msg) // "Hello world!"
      });
```

Have fun coding :)