Last updated : Dec 12, 2021
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