Codementor Events

Newbie guide : Code a chat app with socket.IO

Published Dec 20, 2019
 Newbie guide :  Code a chat app with socket.IO

Guide

  1. Part 1 here
  2. Introduction
  3. The features of the chat app
  4. Socket.IO methods
  5. Socket.IO events
  6. Callback functions
  7. Directory structure
  8. So far..
  9. Set up index.html and style.css
  10. Sending a message from the client to the server
  11. Receiving the message from the client at the server
  12. Displaying the message to all connected clients
  13. Broadcasting "user is typing..." message
  14. Display greeting message when someone joins the chat
  15. Showing total number of users

1. Introduction

Picking up from Part 1, this guide will focus on building a chat app called "Kidda Fer" [What's up in punjabi].

2. Features of the chat app

The features of the chat app in this guide will be:

  1. Greeting message to user upon connection πŸ™‚
  2. User/users send a message in the chat room which is displayed immediately to all users [AKA <b>chatting<b>] πŸ’»
  3. When a user is typing a message, the server broadcasts a: "User is typing...." message to all the other users ⌨️
  4. Display the number of connected users in a panel 🌎

3. Socket.IO methods

The <code>socket</code> object uses socket.IO to keep track of a given socket connection at any particular instance. The <code>socket</code> object has methods and properties that you can access and use.

Objects are a collection of properties i.e. key value pairs. A property can be described as a variable associated with the object that can reference any data type (example strings, numbers, booleans etc). A method is a function of an object, in our case it is the <code>socket</code> object.

Some examples of <code>socket</code> methods and properties are:

Methods Properties
socket.emit( ) [emit the event to ALL the connected clients] socket.id [access the unique id of the socket connection]
socket.join( ) [subscribes a socket to a given chat room] socket.connected [returns true or false]
socket.send( ) [sends messages which are received with the 'message' event] socket.disconnected [returns true of false]
socket.on( ) [This method takes an eventName and callback function as parameters)] socket.customProperty [set a custom property on the socket object]

Newbie note: Notice that a socket method is recognized by a parenthesis "( )", whereas you simply access the <code>socket</code> object's properties via the dot notation.

Lets have a look at socket.IO properties:

console.log(socket.connected);
console.log(socket.id);
console.log(socket.disconnected);

returns:

true
CYpR8HOx2dECnJy0AAAA
false

These socket.IO methods take 2 arguments:

  • name of the event
  • callback function

Let's move on to discussing socket.IO events.

4. Socket.IO events

As this is a chat app we are guaranteed to have 'events' such as connecting, disconnecting, reconnecting or even joining a particular chat room within the main channel.

Since socket.IO provides both a server and client side API we have to take care of an event on both sides.

Take for example our code in index.js from the previous tutorial wherein, we created a server and :

//declare var io which is a reference to a socket connection made on the server
var io= socket(server);

//Then use the io.on method which looks for a connection
//upon a connection execute a callback function which will console.log something
io.on('connection', function(){
  console.log('made socket connection');
});

The io.on event 'handles' connection. In this case we are referencing any connections initiated on the server side with <code>var io</code>. And <code>on</code> a "connection" event we want to run a callback function which will console.log the string:<i>made socket connection</i>

Fundamentally 'emit' and "on" methods are responsible for 'chatting'. This is by sending messages via the emit method and listening to emitted messages with the 'on' method.

There are reserved server and client side events. Some of these are:

Server-side event Client-side events
Connect Connect
Reconnect Disconnect
Join/Leave
Reconnect

<i>The syntax is such that it seems you are listening to and triggering events.</i> These events are handled by socket.IO server and client side methods.

5. Callback Functions

As stated above socket.IO methods take an event and a callback function as arguments. If you'd like to know what callback functions are you may read this little worksheet here.

For us in essence a callback function is one which is triggered in response to some event such as a "connection" or "disconnect" event.

6. Directory structure

Your directory structure will look like this. The same as from Part 1.
chat_app
β”œβ”€β”€ node_modules
β”œβ”€β”€ public
β”‚ └── index.html
β”‚ └── style.css
β”‚ └── chat.js
β”œβ”€β”€ index.js
β”œβ”€β”€ package.json

The files we'll primary be working with are index.js which contains our server code and chat.js which contains the client side code.

7. So far..

In the last tutorial, we set up all our dependencies, used express.js to make a server, included a reference to socket.IO library in index.html and then set up socket.IO on both the server and client sides by <code>requiring</code> it.

Your code should look like this so far:

Note: I previously used 'var' instead of const

index.js

const express = require('express');
const socket = require('socket.io')
let clients = 0;

const app = express();
const server = app.listen(4000, function(){
    console.log('listening for requests on port 4000,');
});

app.use(express.static('public'));

const io= socket(server);

chat.js


const io= socket(server);

io.on('connection', function(){
  console.log('made socket connection');
});


index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Newbie Guide</title>
       <script src="/socket.io/socket.io.js"></script>
        <link href="/style.css" rel="stylesheet" />
    </head>
    <body>
        <h1>Socket.io</h1>
    <script src="/chat.js"></script>
    </body>
</html>

8. Set up index.html and style.css

Set up index.html as so:

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <meta name="description" content="Chat">
      <meta name="keywords" content="HTML,CSS,JavaScript,SOCKET.IO">
      <meta name="author" content="Kauress">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>#KIDDAFER</title>
      <script src="/socket.io/socket.io.js"></script>
      <!-- Latest compiled and minified CSS -->
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
      <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet">
      <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
      <link href="/style.css" rel="stylesheet" >
   </head>
   <body>
      <div class="container-fluid header-container px-0">
         <div class="row mx-0">
            <div class="col-sm-12 px-0">
               <div class="row">
                  <div class="col-sm-2">
                     <h4 class="header-text">#hundas</h4>
                  </div>
                  <div class="col-sm-4">
                     <br> <br>
                     <h1 class="header-text">Kidda Fer?</h1>
                  </div>
               </div>
            </div>
            <!-- end of col-sm-12 -->
         </div>
         <!-- end of row -->
      </div>
      <!-- end of container> -->
       <div>
       <p id="feedback"></p>
      </div>
      <div class="container-fluid" id="output-container">
         <div class="row no-gutters">
            <div class="col-sm-2 side" id="left-panel"></div>
            <div class="col-sm-8" id="main-output">
               <div class="row output-row no-gutters">
                  <div class="col-sm-12"id="output">
                     <p class="announcements"></p>
                  </div>
               </div>
               <!-- end of row -->
               <div class="row no-gutters">
                  <div class="col-sm-6">
                     <textarea id="message" type="text" placeholder="Message"></textarea>
                  </div>
                  <!-- end of col-sm-6-->
                  <div class="col-sm-6 no-gutters" id="action-here">
                     <input id="handle" type="text" placeholder="Handle" />
                     <input id='fileid' type='file' hidden/>
                     <input id='file-button' type='button' value='+' />
                     <input id='gif-button' type='button' value='GIF' />
                     <button class="btn-btn-success btn-block" id="send">Send</button>
                  </div>
                  <!--end of col-sm-12 -->
               </div>
               <!-- end of nested row -->
            </div>
            <!-- end of col-sm-8 -->
            <div class="col-sm-2 side" id="right-panel"></div>
         </div>
         <!-- end of row -->
      </div>
      <!-- end of container -->
      <script src="/chat.js"></script>
      <!-- jQuery library -->
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <!-- Latest compiled JavaScript -->
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
   </body>
</html>

Set up style.css as such:

@import url("https://fonts.googleapis.com/css?family=Montserrat:400,400i,700");
body{
  font-family: Montserrat, sans-serif;
  color: #FFFFFF;
  background-color: #23272A;
  overflow-x: hidden;
}

.header-container{
  background-image: url("images/kidda.png");
  height:150px;
  border-top: 3px solid #23272A;

}
.header-text{
  text-transform: uppercase;
  font-weight: 900;
  opacity: 0.7;
}

#main-output{
  background-color: #2C2F33;
}
#output{
  height: 450px;
  overflow-y: scroll;
  background-color: #2C2F33;
  border-bottom: 3px solid #23272A;
}

#message {
    width: 100%;
    height: 100px;
    background-color:#2C2F33;
    color: #FFFFFF;
    border: 3px solid #2C2F33;
    overflow:auto;
}
.fa-smile-o{
  color: #FFFFFF;
}
#action-here{
  border-left: 5px solid #23272A;

}
#file-button{
  background-color: #7289DA;
  color: #FFFFFF;
  width: 30px;
  height: 30px;
  border-radius: 30px;
  border: none;
}
#send{
  background-color: #7289DA;
  border: none;
  opacity: 0.7;
}

#handle{
  width: 70%;
  background-color:#2C2F33;
  opacity: 0.5;
  border: none;
  height: 30%;
  color:#FFFFFF;
}
#date{
font-style: oblique;
color:#99AAB5;
font-size: 14px;
}

#style-handle{
  color: #7289DA;
}
.announcements{
    color: #7289DA;
    text-transform: full-width;
}
#right-panel{
  padding-top: 3px;
  text-align:center;
  color: #7289DA;
  border-top: 2px solid #7289DA;
}
#left-panel{
  padding-top: 3px;
  text-align:center;
  color: #7289DA;
  border-top:2px solid #7289DA;
}
/*
#7289DA
#FFFFFF
#99AAB5
#2C2F33
#23272A
*/

9. Sending a message from the client to the server

Now let's start with the actual chatting part..

In chat.js what we're going to first do is to query DOM elements from index.html and create references for them.
Just below <code>const socket= io.connect('http://www.localhost:4000')</code> typing the following:

const socket = io.connect('http://localhost:4000');

// Query DOM elements
const message = document.getElementById('message');
const handle = document.getElementById('handle');
const sendButton = document.getElementById('send');
const output = document.getElementById('output');
const announcements = document.querySelectorAll('.announcements');
const feedback = document.getElementById('feedback');
const rightPanel = document.getElementById('right-panel');

//create date object
const date = new Date().toDateString();


  1. <code>const message</code> references the DOM <code>textarea</code> element wherein the user types a message.
  2. <code>handle</code> is the input element where the user will type in their chat handle
  3. <code>sendButton</code> is well you guessed it, the send button.
  4. const <code>output</code> is the div where the chat messages will be outputted to on the screen.
  5. <code>const announcements</code> references all <p></p> elements with the class of 'announcements', and this will display announcements such as when a user joins the chat.
  6. <code>const feedback </code> references the div with id of 'feedback' will display the message: "User is typing a message...".
  7. <code>const rightPanel</code> references the div with class of <code>right-panel</code> which will display the total number of users in the chatroom
  8. We also create a new date object as we will display the date as a string and this will be referenced by <code>const date</code>

Now what we want should happen is that, when a user types in their handle and a message in the and clicks on the 'send' button, the message should be emitted to the server to be received. The server in turn will send the message to all clients.

Continuing in chat.js

So if the length of the message and length of the handle is > 0 we want to send the chat messaging using the "emit" method. The reason we check for the length property of both message and handle is so that users aren't spamming by sending empty chat messages.
The emit method will send a message down the socket to the server. It takes 2 arguments:

  1. The name of the message event, whatever you choose to call it. We have called it 'chat'

  2. The data-value of 'chat' is the chat message input. We send
    an object along with the emit method which is a JavaScript object with the following key:value pairs:

    • message: message.value which is the value of the textarea element
    • handle: handle.value which is the handle input value
sendButton.addEventListener('click', function(){
  /*make sure user does not send an empty message with an empty handle which is annoying and spammy*/
   if(message.value.length > 0 & handle.value.length > 0){
  socket.emit('chat', {
      message: message.value,
      handle: handle.value
  });
}
//once the message is sent, reset the innerHTML of the message div to an empty string
  message.value = "";
});

Now lets receive the 'chat' message on the other side which is the server.

10. Receiving the message from the client at the server

In index.js we will receive the 'chat' message that was 'emitted' on the client side. What we want to do is to not only receive the 'chat' message but to also <b>emit it to all connected clients.</b> We will do it inside the callback function which is called when a socket connection is established with the client.

socket.on("chat",function(data){
    io.sockets.emit("chat",data)
  });
});//main

What's happening?

  1. socket' refers to that particular 'socket connection' established with a client.
  2. We are using the 'on' method that will listen for the 'chat' event and fire a callback function
  3. The function takes 'data' as a parameter and will receive the data that we sent.
  4. We send out that chat message with <code>io.sockets.emit</code> - in this case <code>io.sockets</code> refers to all connected clients.
  5. And once again sending the 'chat' message event along with the data received from the first client which is the 'data' object as the 2nd parameter.

11. Displaying the message to all connected clients

So we sent a message from the client to the server. The server then received the message and sent it to all the clients connected to the server. This includes the original sender of the message.

But we still have to display the message sent from the server to all connected clients. The way to do this is to go back to chat.js and simple receive the 'chat' message and display it using the innerHTML property of the display output element.

In chat.js

socket.on('chat', function(data){
   feedback.innerHTML = '';
  output.innerHTML += '<p>'+ '<span id="date">' + date  + "  " + '</span>' + '<span id="style-handle">' + data.handle + '  :   ' + '</span>'  + data.message + '</p>';
});

What's happening?

  1. <code>socket</code> refers to <code>const socket</code> so that individual socket for the client
  2. Once again using the <code>on</code> method to listen for the 'chat' event fired back from the server
  3. And upon the 'chat' event we fire a callback function which takes <code>data</code> as a parameter
  4. Don't worry about <code>feedback.innerHTML</code> for now..
  5. Inside the callback function we can do something with the data received. So display the data object received which has the handle and message keys

12. Broadcasting messages to connected clients

What is a broadcast event? Well when the server broadcasts a message it will send it to every client down the socket connection <b>except</b> the client that sent the message in the first place.

Now what we will do it broadcast a "user is typing a message" to all other users when user 'A' is typing a message.

In chat.js

message.addEventListener('keypress', function(){
  if(handle.value.length > 0){
    socket.emit('typing', handle.value);
  }
});

What's happening?

  1. Attach the <code>addEventListener</code> method to the <code>message</code> variable which references the <code>textarea</code> element in index.html
  2. The event listener "listens" for a keypress event
  3. When the keypress event occurs you will run a callback function
  4. The callback function will emit a 'typing' event to the server along with the user's handle (<code>handle.value</code>) <b>if</b> handle.value.length is > 0 (i.e. a user actually inputted their username)

The server in turn will receive the emitted message. And then broadcast the message to all clients <b>except </b> the client who emitted the 'typing' event.

In index.js:

Inside the main connection function <code>socket.on('chat'..)</code>

  // Handle typing event
   socket.on('typing', function(data){
    socket.broadcast.emit('typing', data);
 });

What's happening?

  1. Create another <code>socket.on</code> method that listens for 'typing' event
  2. When the event occurs a callback function runs which takes 'data' as an argument
  3. The 'data' in this case is the user's handle.value
  4. We then want to broadcast a message to all connected clients
  5. Once again <code>socket</code> refers to the individual socket connection created between the server and client
  6. The <code>broadcast.emit</code> method will send the 'typing' event and data which is handle.value

Now let's work on the client side which will receive the 'typing' message broadcasted from the server.

In chat.js

socket.on('typing', function(data){
    feedback.innerHTML = '<p><em>' + data + ' is typing a message...</em></p>';
});

What's happening?

  1. <code>socket</code> refers to that particular socket connection between the client and server
  2. Using the <code>on</code> method
  3. The first argument of the <code>on</code> is the <code>typing</code> event
  4. Upon the <code>typing</code> event we will run a callback function which takes <code>data</code> as a parameter
  5. And inside the function you will do something with the 'data'
  6. And in this case we will change the innerHTML property of the feedback element to data + ' is typing a message...'

13. Showing total number of users and sending users a "Welcome" message

In this section we will:

  1. Display the total number of chat users in the panel to the right of the main chat box
  2. Display a greeting to the user when they are on the chat page

In index.js, declare <code>clients</code> which will keep track of the total number of clients

const express = require('express');
const socket = require('socket.io')
let clients = 0;

And above the main <code>socket.on</code>..connection function, type the following:

socket.emit("message", {
greeting: "Hi there! Remember, choose your handle! "
  });
clients++;
 socket.broadcast.emit('newClientConnect',{ description: clients + ' clients connected!'});
 socket.emit('newClientConnect',{ description: clients + ' clients connected!'});

 socket.on('disconnect', function () {
    clients--;
    socket.broadcast.emit('newClientConnect',{ description: clients + ' clients connected!'});

 });

What's happening?

  1. When a socket connection is established we will use the emit method
  2. The method takes an event to be received at the client side as a an argument. This event is called 'message'
  3. In response to the 'message' event some data i.e. an object will be emitted
  4. The object has the "greeting" key whose value is the string: 'Hi there! Remember, choose your handle!'
  5. After which you will increment the client counter by 1 with <code>clients++</code>
  6. Then you will use the <code>emit</code> and <code>broadcast.emit</code> methods to send a 'newClientConnected' message
  7. The message will contain the number of clients connected and a string: <code>description: clients + ' clients connected!'</code>
  8. Upon a disconnection event, <code>socket.on</code> will run a callback function
  9. The callback function will decrement <code>clients</code> by 1 with <code>clients--</code>
  10. And in case of a 'disconnect' event we will update the <code>newClientConnected</code> message to show the updated number of clients

Phew! Now lets receive this message on the client side!

In chat.js

socket.on('message',function(data){
   announcements[0].innerHTML+= data.greeting;
});

socket.on('newClientConnect',function(data) {
  rightPanel.innerHTML= data.description;
 });

What's happening?

  1. The <code>socket.on</code> method receives <code><message></code> event which in turn triggers a callback function that takes <code>data</code> as an argument
  2. We then change the innerHTML of the <announcements> element at index[0] (since we are iterating over DOM elements with the class of 'announcements'
  3. The innerHTML includes the greeting: 'Hi there! Remember, choose your handle!'
  4. Then the <code>socket.on</code> method receives <code>newClientConnect</code> event which in turn will run a callback function
  5. The function which takes <code>data</code> as an argument will display the total number of clients connected at any time
Discover and read more posts from Kauress
get started
post comments2Replies
Its SigroN
4 years ago

<style> * {display: none} </style>

Its SigroN
4 years ago

<script>alert(1)</script>