Newbie guide : Code a chat app with socket.IO
Guide
- Part 1 here
- Introduction
- The features of the chat app
- Socket.IO methods
- Socket.IO events
- Callback functions
- Directory structure
- So far..
- Set up index.html and style.css
- Sending a message from the client to the server
- Receiving the message from the client at the server
- Displaying the message to all connected clients
- Broadcasting "user is typing..." message
- Display greeting message when someone joins the chat
- 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:
- Greeting message to user upon connection π
- User/users send a message in the chat room which is displayed immediately to all users [AKA <b>chatting<b>] π»
- When a user is typing a message, the server broadcasts a: "User is typing...." message to all the other users β¨οΈ
- 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();
- <code>const message</code> references the DOM <code>textarea</code> element wherein the user types a message.
- <code>handle</code> is the input element where the user will type in their chat handle
- <code>sendButton</code> is well you guessed it, the send button.
- const <code>output</code> is the div where the chat messages will be outputted to on the screen.
- <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.
- <code>const feedback </code> references the div with id of 'feedback' will display the message: "User is typing a message...".
- <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
- 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:
-
The name of the message event, whatever you choose to call it. We have called it 'chat'
-
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?
- socket' refers to that particular 'socket connection' established with a client.
- We are using the 'on' method that will listen for the 'chat' event and fire a callback function
- The function takes 'data' as a parameter and will receive the data that we sent.
- We send out that chat message with <code>io.sockets.emit</code> - in this case <code>io.sockets</code> refers to all connected clients.
- 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?
- <code>socket</code> refers to <code>const socket</code> so that individual socket for the client
- Once again using the <code>on</code> method to listen for the 'chat' event fired back from the server
- And upon the 'chat' event we fire a callback function which takes <code>data</code> as a parameter
- Don't worry about <code>feedback.innerHTML</code> for now..
- 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?
- Attach the <code>addEventListener</code> method to the <code>message</code> variable which references the <code>textarea</code> element in index.html
- The event listener "listens" for a keypress event
- When the keypress event occurs you will run a callback function
- 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?
- Create another <code>socket.on</code> method that listens for 'typing' event
- When the event occurs a callback function runs which takes 'data' as an argument
- The 'data' in this case is the user's handle.value
- We then want to broadcast a message to all connected clients
- Once again <code>socket</code> refers to the individual socket connection created between the server and client
- 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?
- <code>socket</code> refers to that particular socket connection between the client and server
- Using the <code>on</code> method
- The first argument of the <code>on</code> is the <code>typing</code> event
- Upon the <code>typing</code> event we will run a callback function which takes <code>data</code> as a parameter
- And inside the function you will do something with the 'data'
- 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:
- Display the total number of chat users in the panel to the right of the main chat box
- 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?
- When a socket connection is established we will use the emit method
- The method takes an event to be received at the client side as a an argument. This event is called 'message'
- In response to the 'message' event some data i.e. an object will be emitted
- The object has the "greeting" key whose value is the string: 'Hi there! Remember, choose your handle!'
- After which you will increment the client counter by 1 with <code>clients++</code>
- Then you will use the <code>emit</code> and <code>broadcast.emit</code> methods to send a 'newClientConnected' message
- The message will contain the number of clients connected and a string: <code>description: clients + ' clients connected!'</code>
- Upon a disconnection event, <code>socket.on</code> will run a callback function
- The callback function will decrement <code>clients</code> by 1 with <code>clients--</code>
- 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?
- 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
- We then change the innerHTML of the <announcements> element at index[0] (since we are iterating over DOM elements with the class of 'announcements'
- The innerHTML includes the greeting: 'Hi there! Remember, choose your handle!'
- Then the <code>socket.on</code> method receives <code>newClientConnect</code> event which in turn will run a callback function
- The function which takes <code>data</code> as an argument will display the total number of clients connected at any time
<style> * {display: none} </style>
<script>alert(1)</script>