How to create a chat server for doing real-time group chats?

How to create a chat server for doing real-time group chats?

Using socket.io & vue js

In this tutorial, we'll use socket.io to build our real-time chat server & implement our group chat on top of it.

In Socket.io, there is a server-side concept called "room" that we can use for our chat room to have a group chat feature. These rooms are arbitrary channels that sockets can join and leave.

To get more theory over "room" in socket.io you can check their official doc at socket.io/docs/v4/rooms

Now, to design this feature we've to make two modules. One is for our client-side, where users can send & receive their group chats messages. Another one is the server-side where our socket server will handle all the client sockets & respective messages of them. All client module files will be inside the "client" folder & server module files will be inside the "server" folder as shown in the below picture.

Screenshot 2022-02-03 at 3.24.18 PM.png

Client Module: On the client-side, we've our HTML file that holds all the chatbox designs & necessary CSS, JS files in the "asset" folder. For CSS we are using bootstrap CSS & in JS we are "Vue JS" for all front-end dom-manipulation & "moment.js" for our message timestamp.

To open our client-side file(chatty.html) we can use any of our web servers as shown in the following URL. For the demo purpose, we are passing user id(uid), room id(rid) & room name(rname) as URL parameters.

http://127.0.0.1:8000/chatty.html?uid=1&rid=1&rname=JS

In our client-side Javascript code(app.js), we are first trying to connect to our WebSocket server using this line -

var socket = io('http://localhost:3000');

Then, trying to fetch the user & room information from the URL by using

const urlParams = new URL(location.href);
const rid = urlParams.searchParams.get('rid');  // room id
const rname = urlParams.searchParams.get('rname');  // room name
const uid = urlParams.searchParams.get('uid');  // user id

for the demo purpose, we've hardcoded the user's data in the file at -

users = [
    {
        id:1,
        name: 'Tom Alter',
        pimg: '1.png'
    },
    {
        id:2,
        name: 'Sharon Lessman',
        pimg: '2.png'
    },
    {
        id:3,
        name: 'Fiona Green',
        pimg: '3.png'
    }
];

Then at the end, we've our all Vue JS code which we use to render the page after getting all the data from the server.

var app = new Vue({
    el: '#app',
    data: {        
        roomID: false,
        roomName: false,
        loginUserID: parseInt(uid),
        loginUserImg: '',
        loginUserData:[],
        usersInOnline:[],
        userMsg: '',
        chats:  []
    },
    methods: {
        sendUserMsg(e) {
            let userMsg = this.userMsg;
            this.userMsg = '';

            let userID = this.loginUserData[0].id;
            let userName = this.loginUserData[0].name;
            let userImg = this.loginUserData[0].pimg;
            let roomID = rid;
            let roomName = rname;
            let msgObj = {userID, userName, userImg, userMsg, roomID, roomName};                    
            socket.emit('msgSent', msgObj);

            let dateTime = moment().format('h:mm A');
            let totalMsgs = this.chats.length;
            let userMsgObj = {
                id: totalMsgs++,
                fromID: userID,
                fromName: userName,
                pimg: userImg,
                msg: userMsg,
                dateTime: dateTime
            };
            this.chats.push(userMsgObj);
        },
    },
    mounted: function() {
        const user = users.filter(user => user.id === parseInt(uid));

        this.loginUserData = user;
        this.loginUserImg = user[0].pimg;
        let roomData = {
            userName: user[0].name,
            userID: this.loginUserID,
            roomID: rid,
        };

        socket.emit('userJoinRoom', roomData);

        socket.on('botMsg', (data) => {
            let userName = data.userName;
            let userMsg = data.userMsg;
            let userImg = data.userImg;
            let dateTime = data.dateTime;

            let totalMsgs = this.chats.length;

            let botMsg = {
                id: totalMsgs++,
                fromID: 0,
                fromName: userName,
                pimg: userImg,
                msg: userMsg,
                dateTime: dateTime
            };
            this.chats.push(botMsg);
        });

        socket.on('usersInOnline', (data) => {            
            this.usersInOnline = data;
        });

        socket.on('userMsg', (data) => {
            let userID = data.userID;
            let userName = data.userName;
            let userMsg = data.userMsg;
            let userImg = data.userImg;
            let dateTime = data.dateTime;    

            let totalMsgs = this.chats.length;

            let msg = {
                id: totalMsgs++,
                fromID: userID,
                fromName: userName,
                pimg: userImg,
                msg: userMsg,
                dateTime: dateTime
            };
            this.chats.push(msg);
        });
    }
})

Server Module: Our node project has socket.io as our project dependency on the server-side.

To install all the dependencies & start the socket server use following commands

# install dependencies
$ npm install

# serve with hot reload at localhost:3000
$ npm run dev

# build for production and launch server
$ npm run build
$ npm run start

To handle the CORS situation on the server-side we are using the following piece of code.

const httpServer = createServer();
const io = new Server(httpServer, {
    cors: {
        origin: "http://127.0.0.1:8000",
        methods: ["GET", "POST"],
    }
});

Besides this, on the server-side, we've all our following event listener code

io.on("connection", (socket) => {

    // when user join the server
    socket.on('userJoinRoom', (data) => {        
        let userName = data.userName;
        let roomID = data.roomID;
        let userID = data.userID;

        socket.join(roomID);

        let botMsgObj = { 
            userName:'Bot', 
            userImg:'bot.png',
            userMsg: "Welcome to Chat Server",
            dateTime: moment().format('h:mm A')
        };
        socket.emit('botMsg', botMsgObj);

        let userData = {
            id: socket.id,
            userName: userName,
            roomID: roomID,
            userID: userID,
            pimg: `${userID}.png`
        };
        usersInOnline.push(userData); 
        io.emit('usersInOnline', usersInOnline);

        botMsgObj = { 
            userName:'Bot', 
            userImg:'bot.png',
            userMsg: `"${userName}" has joined the room`,
            dateTime: moment().format('h:mm A')
        };
        socket.broadcast.to(roomID).emit('botMsg', botMsgObj);
    });

    // when received user message
    socket.on('msgSent', (data) => {
        let userID = data.userID;
        let userName = data.userName;
        let userImg = data.userImg;
        let roomID = data.roomID;
        let userMsg = data.userMsg;
        let dateTime =  moment().format('h:mm A');

        socket.join(roomID);

        let msgObj = {userID, userName, userImg, userMsg, dateTime};
        socket.broadcast.to(roomID).emit('userMsg', msgObj);
    });

    // when user left chat room
    socket.on('disconnect', () => {
        let newUserList = [];
        let userName = '';
        let roomID = '';
        usersInOnline.forEach((user) => {
            if(user.id != socket.id){
                newUserList.push(user);
            }else{
                userName = user.userName;
                roomID = user.roomID;
            }
        });
        usersInOnline = newUserList;
        io.emit('usersInOnline', usersInOnline);

        botMsgObj = { 
            userName:'Bot', 
            userImg:'bot.png',
            userMsg: `"${userName}" has left the room`,
            dateTime: moment().format('h:mm A')
        };
        socket.broadcast.to(roomID).emit('botMsg', botMsgObj);
    });
});

How it works: Whenever any client opens out chat page we emit an event called "userJoinRoom" from our client-side JS(app.js) code. Then on the server-side, we catch that "userJoinRoom" event & we emit three more events in response.

Those are -

  1. socket.emit('botMsg', botMsgObj);
  2. io.emit('usersInOnline', usersInOnline);
  3. socket.broadcast.to(roomID).emit('botMsg', botMsgObj);

1. socket.emit('botMsg', botMsgObj): This event is for sending a greeting message to the user who opens the chat page. On the client side on getting off, this event bot will print the message in the message box.

2. io.emit('usersInOnline', usersInOnline): This one is for rendering all the online users list.

3. socket.broadcast.to(roomID).emit('botMsg', botMsgObj): This will notify all other users of the group about the current user's joining to the group.

Again, on the client-side, whenever the user writes any message & click on send button, we create an event called "userMsg" which gets caught by our server-side event listener & broadcast to all the other connecting users of the room.

To show all online users whenever users join & leave from the room we collect their data & send it to the client-side by emitting the "usersInOnline" events to populate those.

You can get the full code from the following Github link. You can go through the code & modify that according to your own requirements.

Github: github.com/mi6crazyheart/chat-server-for-re..

For any kind of queries, you can comment down. I'll try to answer them one by one.

To know more about such development information, follow me @ -