Real time chat application using socket.io in node.js

Last updated : Dec 11, 2021

Real time chat application using socket.io in node.js



Real time chat applications have become a trend in programming and largely in customer support system for businesses. This article will explain you how to create a real time chat application using socket.io in node.js with express framework.


You can also use Node's native web sockets to implement real time nature to your application. But we will be using a very good library called Socket.IO. This library not only makes your real time development swift but also easy.


About the application
1. Firstly this application does not contain a professional UI.

2. This development focuses on backend functionalities in application.

3. This application does not include data persistence because the application only shows current messages and when application shut, data is removed but for unread messages badge, we have used internal storage to store messages temporarily.

4. Flow of the application starts from join event which is sent from client to server and server broadcasts the message to all the users connected.

5. After broadcasting the message system also refreshes the user list.

6. Then when any user sends the message to other user, send_msg event id fired and server broadcasts the message to other user then on client level , the system checks if sender window is opened at receiver end then message appends to list otherwise the message is saved in local storage and it will show it as badge in front of that user in list.

7. Same at sender end the message is appended in the list.


That was the theory about the application. Let's start scripting.


1. Setup the directory
Open up a terminal and make a dir called open-chat

mkdir open-chat


2. Create files and install dependencies
In the terminal change directory to open-chat and type in

cd open-chat
npm -i express socket.io --save
And also create a file named as server.js


3. Setup server.js scripting
Open the file in any code editor and type in

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

var users = [];

app.use(express.static(__dirname + '/view'));

app.get('/',function(req,res){
    res.sendFile('index.html');
});

io.on('connection',function(socket){
    //console.log('user connected');
    var nm;
    
    socket.on('joined',function(user_data){
        nm = user_data.name;
        io.emit('joined_response',nm);
        console.log(user_data.name+" joined");        
        socket.join(user_data.name);
        users.push(user_data.name);
        console.log(users);
        for(var i=0;i<users.length;i++){
            io.to(users[i]).emit('refresh_users',users);
        }
    });

    socket.on('send_msg',function(user_signed_in,current_user,msg){
        io.to(user_signed_in).to(current_user).emit('sent_msg',user_signed_in,current_user,msg);
    });

    socket.on('typing_to_server',function(sender,typing_status){
        io.emit('typing_to_client',sender,typing_status);
    });
    
    
    socket.on('disconnect',function(){
        console.log(nm+' user disconnected');
        for(var i=0;i<users.length;i++){
            if(users[i] == nm){
                users.splice(i,1);
            }
        }
        for(var i=0;i<users.length;i++){
            io.to(users[i]).emit('refresh_users',users);
        }
        console.log("remaining users : "+users);
        io.emit('disconnect_response',nm);
    });
});

http.listen(3000,function(){
    console.log('on port : 3000');
});


Before proceeding, let's crack up the code very briefly

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);

In the above code we have initialized the packages constructors.


var users = [];
app.use(express.static(__dirname + '/view'));

In the above line we have declared and initialized a variable for storing all users. In the second line we are signalling server to use complete directory name with view directory for static files.


app.get('/',function(req,res){
    res.sendFile(__dirname + '/view/index.html');
});

In the above lines a route has been scripted, because we are creating single page app.


io.on('connection',function(socket){
    var nm;

    socket.on('joined',function(user_data){
        nm = user_data.name;
        io.emit('joined_response',nm);
        console.log(user_data.name+" joined");        
        socket.join(user_data.name);
        users.push(user_data.name);
        console.log(users);
        for(var i=0;i<;users.length;i++){
            io.to(users[i]).emit('refresh_users',users);
        }
    });

    socket.on('send_msg',function(user_signed_in,current_user,msg){
        io.to(user_signed_in).to(current_user).emit('sent_msg',user_signed_in,current_user,msg);
    });

    socket.on('typing_to_server',function(sender,typing_status){
        io.emit('typing_to_client',sender,typing_status);
    });


    socket.on('disconnect',function(){
        console.log(nm+' user disconnected');
        for(var i=0;i<;users.length;i++){
            if(users[i] == nm){
                users.splice(i,1);
            }
        }
        for(var i=0;i<;users.length;i++){
            io.to(users[i]).emit('refresh_users',users);
        }
        console.log("remaining users : "+users);
        io.emit('disconnect_response',nm);
    });
});

In the above lines, each and every action event is handled which is triggered by client and vice versa. Starting with connection, everything starts with connection, this event is not actually triggered but is a starting point for initializing the Socket programming.

Then comes joined event followed by parameter which is an object user_date which contains the name of the user. Then it fires joined_response event to process that at client's end.

Then there is an event send_msg. This is triggered by client when any user sends message to other. This takes 3 parameters sender, receiver and message. Then system broadcasts message to only those users by emitting sent_msg event.

Then comes an interesting event typing_to_server which is triggered by client and processed at server and is broadcasted to receiver. "Typing" feature is included in modern day chat applications.

Then comes the last event which is disconnect which simply gives response to client by giving the user_list which is refreshed at client side with remaining users.



4. Now let's script client's view

Create a view directory and create an index.html file and Open the file in any code editor and type in

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <title>Open Chat</title>
    <link rel="stylesheet" href="open-chat.css">
</head>
<body>
    <div class="container">            
        <div class="row">
            <div class="col-sm">
                <div class="alert alert-success" id="notif_div" style="display: none;">sfgs</div>
            </div>
        </div>

        <div class="row" id="chat_frm" style="margin-top:100px;">
            <div class="col-sm-4"></div>
            <div class="col-sm-4">  
                <div class="card">  
                    <div class="card-body">
                        <label>Enter your name to join chat</label>
                        <input type="text" id="nm" class="form-control" />            
                        <button class="btn btn-primary" onclick="join()" style="margin-top: 10px;width:100px;">Join</button>
                    </div>
                </div>
            </div>
        </div>

        <div id="chat_win" class="card" style="display:none;">

            <div class="card-body" style="padding:0;">
                <div class="row no-gutters" id="msg_window">
                    <div class="col-sm-4" id="user_container">
                        <div id="current_user_div" style="width:auto;font-size:1.3em;padding:10px 14px;background-color:rgb(220, 220, 220);">
                            ...                    
                        </div>
                            
                        <ul id="user_list" class="list_holder">
                            <li class="user_list_item">sample user</li>
                        </ul>
                    </div>

                    <div class="col-md" id="chat_container">
                        
                        <div id="chatting_with_div" style="font-size:1.3em;width:auto;padding:10px 14px;background-color: rgb(220, 220, 220);">
                            ...
                        </div>
                        <span id="typing_span" style="display:none;width:auto;padding:10px 14px;background-color: aliceblue;">typing ...</span>    
                        <ul id="msg_list" class="list_holder">                                        
                        </ul>
                    </div>
                </div>
                <div class="card-footer">
                    <div class="row no-gutters" id="msg_win2">                    
                        <div class="col-sm-11">                
                            <input type="text" class="form-control" id="msg_tb" />
                        </div>
                        <div class="col-sm"></div>
                        <div class="col-sm">
                            <button id="send_btn" onclick="send_msg()" class="btn btn-success" disabled>Send</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
    </div>
</body>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<script src="/socket.io/socket.io.js"></script>

<script src="main.js"></script>
</html>



5. Add css styles and socket scripting for frontend

Create 2 new files in view directory i.e., open-chat.css and main.js
Open open-chat.css and type in follwing code

::-webkit-scrollbar {
    width: 5px;
}

::-webkit-scrollbar-track {
    background:rgba(240,240,240,0.3);
}

::-webkit-scrollbar-thumb {
    background: #888;
}

::-webkit-scrollbar-thumb:hover {
    background: #555;
}

.list_holder{
    list-style-type:none;
    height:600px;
    overflow-y: scroll;
    transition: 0.4s;
    padding: 0;
}
.user_list_item{
    text-align: center;
    padding:14px    ;
    background-color: rgb(240, 240, 240);
    margin:0;
    transition: 0.4s;
}
.user_list_item:hover{
    background-color: rgb(200, 200, 200);
}
.msg_list_sent{
    border-radius: 50px 0 50px 50px;
    text-align: left;
    padding: 15px;
    background-color: rgb(2,135,209);
    color:white;
    width: auto;
    float: right;
    clear: both;
    margin-left: 30%;            
    box-shadow: 0 0 15px rgba(0,0,0,0.12);            
    margin-bottom: 10px;
    transition: 0.7s;
    
}
.msg_sent_title{
    float: right;
    font-size: 20px;
}
.msg_list_rec{
    border-radius: 0 50px 50px 50px;
    box-shadow: 0 0 15px rgba(0,0,0,0.12);
    float: left;
    clear: both;
    text-align: left;
    padding: 15px;
    background-color: rgb(240,240,240);
    color:rgb(2,135,209);
    width:auto;
    max-width: 400px;
    margin-right: 30%;
    margin-top:0;
    margin-bottom: 10px;
    transition: 0.7s;
}
.msg_rec_title{
    float: left;
    font-size: 20px;
}

.card-footer{
    background-color: rgb(220, 220, 220);
}
.imp_notif{
    text-align: center;
    width: 100%;
    float: left;
    font-size: 20px;
    background-color: rgb(114, 184, 250);
    box-shadow: 0 0 20px rgba(0,0,0,0.12);
    color: white;
    margin: 15px 0 15px 0;
}



Now open main.js and type in following code

var socket = io();
var user_signed_id = null;
var current_user = null;
var local_storage_arr = [];
var local_storage_ob = null;
var unread_count = 0;


//socket functions
socket.on('refresh_users',function(users){
    
    $("#user_list").empty();
    for(var i=0;i<users.length;i++){
        if(users[i] != user_signed_id){
            $("#user_list").append("<li id='"+users[i]+"_item' class='user_list_item'>"+users[i]+"</li>");
        }
    }
});

socket.on('joined_response',function(new_user){
    if(new_user != user_signed_id){
        pushNotification("",new_user+" joined");
    }
});

socket.on('disconnect_response',function(new_user){
    pushNotification("",new_user+" left");
});

socket.on('sent_msg',function(sender,receiver,msg){

    //sending processing
    if(receiver == current_user){
        if(sender == user_signed_id){
            $("#msg_list").append(
                "<li>"
                    +"<div class='msg_list_sent'>"
                        +sender
                        +"<br/>"
                        +msg
                    +"</div>"
                +"</li>"
            );
        }
        //saving the msg in local storage
        local_storage_ob = {
                sender : sender,
                receiver : receiver,
                msg : msg,
                seen_status : 'unseen'
            };
        local_storage_arr.push(local_storage_ob);
        

        var all_msgs = JSON.parse(window.localStorage.getItem("msg"));
        all_msgs.push(local_storage_ob);
        window.localStorage.setItem("msg",JSON.stringify(all_msgs));
    }

    //receiving processing
    if(sender == current_user){
        if(receiver == user_signed_id){
            $("#msg_list").append(
                "<li>"
                    +"<div class='msg_list_rec'>"
                        +sender
                        +"<br/>"
                        +msg
                    +"</div>"
                +"</li>"
            );
            //saving the msg in local storage
            local_storage_ob = {
                sender : sender,
                receiver : receiver,
                msg : msg,
                seen_status : 'seen'
            };
            local_storage_arr.push(local_storage_ob);
            
            window.localStorage.setItem("msg",JSON.stringify(local_storage_arr));
        }
    }
    else{
        if(receiver == user_signed_id){                
            //saving the msg in local storage
            local_storage_ob = {
                sender : sender,
                receiver : receiver,
                msg : msg,
                seen_status : 'unseen'
            };
            local_storage_arr.push(local_storage_ob);
            
            window.localStorage.setItem("msg",JSON.stringify(local_storage_arr));

            var all_msgs = JSON.parse(window.localStorage.getItem("msg"));
            for(var i=0;i<all_msgs.length;i++){
                if((all_msgs[i].sender == sender)&&(all_msgs[i].receiver == receiver)&&(all_msgs[i].seen_status == "unseen")){
                    unread_count++;
                }
            }


            $("#user_list .user_list_item").each(function(){
                var u_nm = this.id;
                var u_nm = u_nm.substr(0,u_nm.indexOf('_'));
                                    
                if(u_nm == sender){
                    var ids = this.id;                        
                    $("#"+ids+" span").remove();
                    $(this).append("<span class='badge badge-light' style='float:right;'>"+unread_count+"</span>");
                    
                }
            });


            unread_count = 0;
            
        }
    }


        
});

socket.on('typing_to_client',function(user_sender,typing_stat){
    if(user_sender != user_signed_id){
        if(typing_stat == "start"){
            $("#typing_span").text(user_sender+" typing ...");
            $("#typing_span").fadeIn();
        }
        else if(typing_stat == "stop"){
            $("#typing_span").fadeOut();
            $("#typing_span").text("");
        }
    }
});




//local functions
setInterval(function(){
    if(current_user == null){
        $("#send_btn").prop('disabled',true);
    }
    else{
        $("#send_btn").prop('disabled',false);
    }
},1000);

function join(){
    
    var name = $("#nm").val();
    if(name == null || name == "" || name.length == 0){
        alert("enter your name");
        return false;
    }


    var user_obj = {name:name};
    
    socket.emit('joined',user_obj);

    $("#chat_frm").fadeOut(30);
    $("#chat_win").fadeIn();

    user_signed_id = name;
    $("#current_user_div").text("You : "+name);

    
}

function send_msg(){
    var msg = $("#msg_tb").val();
    $("#msg_tb").val("");
    socket.emit('send_msg',user_signed_id, current_user, msg);
}   

function pushNotification(notif_type, notif_msg){
    $("#msg_list").append("<li class='imp_notif'>"+notif_msg+"</li>");
    $("#notif_div").text(notif_msg);
    $("#notif_div").fadeIn();
    setTimeout(function(){
        $("#notif_div").fadeOut();
    },1500);
}





//dom event functions
$("#user_list").on('click','li',function (){
    var c_id = this.id;
    $("#"+c_id+" span").remove();
    $("#user_list .user_list_item").each(function(){
        $(this).css({"background-color":"rgb(230,230,230)","color":"black"});
    });
    $(this).css({"background-color":"rgb(2,135,209)","color":"white"});
    current_user = $(this).text();
    current_user_to_show = current_user.charAt(0).toUpperCase()+current_user.slice(1);
    $("#chatting_with_div").text(current_user_to_show);
    
    $("#msg_list").empty()
    document.title = current_user;

    var all_msgs = JSON.parse(window.localStorage.getItem("msg"));
    for(var i=0;i<all_msgs.length;i++){
        if((all_msgs[i].sender == user_signed_id)&&(all_msgs[i].receiver == current_user)){
            $("#msg_list").append(
                "<li>"
                    +"<div class='msg_list_sent'>"
                        +all_msgs[i].sender
                        +"<br/>"
                        +all_msgs[i].msg
                    +"</div>"
                +"</li>"
            );
        }
        else if((all_msgs[i].sender == current_user)&&(all_msgs[i].receiver == user_signed_id)){
            $("#msg_list").append(
                "<li>"
                    +"<div class='msg_list_rec'>"
                        +all_msgs[i].sender
                        +"<br/>"
                        +all_msgs[i].msg
                    +"</div>"
                +"</li>"
            );
            all_msgs[i].seen_status = "seen";
        }
    }
});

$("#msg_tb").focus(function(){
    socket.emit('typing_to_server',user_signed_id,'start');
})

$("#msg_tb").focusout(function(){
    socket.emit('typing_to_server',user_signed_id,'stop');
})

And now the application is scripted and setup and now it's time to run the application. just switch to terminal and move in the open-chat directory and type in nodemon server.js and browse to http://localhost:3000 ot whatever port you are using.



For further reference, please check respository at https://github.com/progssp/realtime-chat-node




Sign in for comment. Sign in