Simple Chat App; socket.io

Hi guys, this would be a 🤞simple tutorial to create a 🤞simple chat app. You know, an application where you can type a message and it pops up on some other computer, yes that one. Well we will try to make it as simple as we can. I’m assuming that you are aware of NodeJS and you have used it before. Well because we would be using NodeJS and a library called socket.io for our endeavor.

So let’s get started

Like I already said I am assuming that you have already installed NodeJS and its package manager on your system ( I will be using NPM in this tutorial ), If not then install it right now because IT IS REQUIRED*.

Create your project folder and create two folders inside it “server” and “client”. We would be using the server folder to host a static express server and a server for socket. In the client folder we would store what we would like to display on the browser.

Client Side
Let’s create an index.html page and add a blank template for this project then add the following snippet of code to your body tag.
File : /client/index.html

<div id="loginForm">
    <input type="text" id="username">
    <button type="button" id="setNameBtn">Set Name</button>
</div>
<button id="disconnectBtn" hidden>Disconnect</button>
 
<div>
    <h1>Chat Area</h1>
    <input type="text" id="msgBox" hidden>
    <button type="button" id="sendBtn" hidden>Send</button>
    <div id="charArea" hidden> </div>
</div>

The idea is whenever the user opens this page we would ask the user their name. Once he is done we would display an input field where he could type in the message and click on the send button. Below this section we would display the messages. We would be manually hiding and unveiling elements using DOM.

Lets include the socket.io client bundle from CDN.
File : /client/index.html

<script src="https://cdn.socket.io/4.5.0/socket.io.min.js" integrity="sha384-7EyYLQZgWBi67fBtVxw60/OWl1kjsfrPFcaU0pp0nAh+i8FD068QogUvg85Ewy1k" crossorigin="anonymous"></script>

Put this script tag at the bottom inside the head tag of your HTML file, to include the socket.io client bundle.

Now let’s create a JavaScript file to handle the events and variables that we would be needing.
Create a file “index.js” in the same folder.
File : /client/index.js

let loginForm = document.getElementById('loginForm');
let loggedIn = false;
 
let username = document.getElementById('username');
let loginButton = document.getElementById('setNameBtn');
 
let disconnectBtn = document.getElementById('disconnectBtn');
 
let charArea = document.getElementById('charArea');
 
let msgBox = document.getElementById('msgBox')
let sendBtn = document.getElementById('sendBtn')

In this we are only defining the DOM elements in the JavaScript variable so we could use them to manipulate the HTML later. We are also using a variable “loggedIn” to store a boolean value to check if the user is logged in or not.

We are creating “login” and “logout” functions In the JavaScript file that we created in the client folder. We need to hide and display the element with the user’s input so it would be something like this. Once the functions are created then bind them to the onclick events of their respective buttons.
File : /client/index.html

function login(){
    if (!username.value || loggedIn) return;
     loggedIn = true;
     loginForm.setAttribute('hidden', true)
     disconnectBtn.removeAttribute('hidden')
     charArea.removeAttribute('hidden')
     msgBox.removeAttribute('hidden')
     sendBtn.removeAttribute('hidden')
   }
loginButton.onclick = login;
 
function logout(){
    if (!loggedIn) return;
   
    loginForm.removeAttribute('hidden');
    charArea.setAttribute('hidden', true)
    charArea.innerHTML=""
    disconnectBtn.setAttribute('hidden', true)
    msgBox.setAttribute('hidden', true)
    sendBtn.setAttribute('hidden', true)
    loggedIn = false;
}
disconnectBtn.onclick = logout;

We would be creating a JavaScript file to handle the events, use the script tag to include this JavaScript file.
File : /client/index.html

<script src="./index.js"></script>

It would be best to put the custom JavaScript file “index.js” script tag at the very bottom of you html file.

Before we go any further, we should create a server to host our files that we are creating.
We would be using express server.

Okay then let us go to the server folder we created earlier. Open the terminal inside and install express using the package manager you have

npm i express
// or
npm install express

Once you have installed express, create a file, name it “static-server.js”. Put the following content in it.
File : /server/static-server.js

const express = require('express');
const app = express();
const port = 3030;
 
app.use('/', express.static( '../client') )
 
app.listen( port, ()=>{
    console.log(`STATIC server http://localhost:${port}`)
} )

We are requiring express and creating an app. And use the express inbuilt server. Express server requires you to pass on the folder of the static pages resides. Once done, we would use listen on port 3030 that we mentioned for connections.

Now we would save the file and run this server via node command. In the terminal go to the server folder and use command “node <filename>”  to start the server. You should then see the console message on the terminal.

D:\_projects\tutorial1\server>node static-server.js server
STATIC server http://localhost:3030

Now we can go to this link to view our html file on the browser.
The login and logout button should be working now. It should be looking something like now.

Now let’s set-up our socket server in our project. So we should start with installing the socket.io package first. The process is the same as we installed the express package.

npm i socket.io
//or
npm install socket.io

Go to the server folder and create a file, name it “socket-server.js”. The code here would be slightly different from the code in the “static-server.js” file.
File : server/socket-server.js

const app = require('express')();
const PORT = 3090;
const httpServer = require('http').createServer(app);
 
httpServer.listen( PORT, async ()=>{
    console.log('SOCKET listening on port :', PORT)
} )

After creating our express app we won’t be listening on it directly for connections. We need to create an http server and use its “createServer” method and pass the express app as a parameter to it. Then we need to listen on the http server we just created.

Here comes the socket.io part. We would require it and pass on the httpServer we created along with an Object with Cors header. Then we would use socket’s “connection” to listen to whenever a connection is made to this server.
File : server/socket-server.js

const httpServer = require('http').createServer(app);
const io = require('socket.io')(httpServer, { cors: { origin:"*" } })
 
io.on( 'connection', async (socket)=>{
    console.log('socket connection successful ', socket.id)
    // socket.handshake.query; //to access the object we sent as a parameter.
    // socket.id // the id the the current socket
} )

So whenever a connection is made, we should see the above console message on the server’s terminal. To access any query that we sent when making the connection can be accessed in “socket.handshake.query”. How would we make a connection you would ask? It’s simple, let us go back to the client’s side javaScript file “index.js” where we wrote login and logout functions. Let’s make a connection function that would make a connection to the socket-server.
File : client/index.js

async function createConnection(){
    socket = io('http://localhost:3090', { query: { name: username.value } } )
    // or just//
    // socket = io('http://localhost:3090') // it takes the URL to the socket server.
}
 
function destroyConnection(){
    socket.disconnect()
}

Now call these functions whenever the user logs in or disconnects. Call createConnection() function from inside the login function.
File : client/index.js

function login(){
    if (!username.value || loggedIn) return;
     loggedIn = true;
     loginForm.setAttribute('hidden', true)
     disconnectBtn.removeAttribute('hidden')
     charArea.removeAttribute('hidden')
     msgBox.removeAttribute('hidden')
     sendBtn.removeAttribute('hidden')
     createConnection()
}

And destroyConnection() from inside from inside the logout function.
File : index.js

function logout(){
    if (!loggedIn) return;
   
    loginForm.removeAttribute('hidden');
    charArea.setAttribute('hidden', true)
    charArea.innerHTML=""
    disconnectBtn.setAttribute('hidden', true)
    msgBox.setAttribute('hidden', true)
    sendBtn.setAttribute('hidden', true)
    loggedIn = false;
    destroyConnection()
}

Now restart the server and go to the browser. Type some name and press “Set Name”. The connection has been made. You can see the socket connection console displaying on the socket server terminal.

PS D:\_projects\tutorial1> cd server
PS D:\_projects\tutorial1\server> node socket-server.js
SOCKET listening on port : 3090
socket connection successful  -VCghby8lelklqSvAAAB

Now let’s make a disconnect handler function.
*You see, whenever a user reloads the page, the socket gets disconnected. So lets create a disconnect handler function to know whenever the user gets disconnected.
File : server/socket-server.js

io.on( 'connection', async (socket)=>{
    console.log('socket connection successful ', socket.id)
 
    socket.on('disconnect', async function(){
        console.log('socket disconnected : ', socket.id)
    })
} )

One more piece of advice from this humble novice; restart the server when you make changes in server files.

Now only things remaining to code is:

  1. Sending message to socket server
  2. Receiving in socket server
  3. Send the message to all the receivers.

Lets create a sendMessage() function in the client’s JavaScript file.
File : client/index.html

function sendMessage(){
    if (msgBox.value){
        socket.emit( 'send-message', {
            username: username.value,
            message : msgBox.value
        })
        msgBox.value = "" //to clear the message box
    }
};
sendBtn.onclick = sendMessage

Here we are using socket’s emit method to emit an event. In socket you can emit events on one side and register listeners on the other. So now we need to listen to this event named “send-message” on the socket server side. We would add this listener just below the disconnect event.
File : server/socket-server.js

socket.on('disconnect', async function(){
        console.log('socket disconnected : ', socket.id)

// listening to "send-message" event from the client side
socket.on( 'send-message', async function( messageObj ){
        io.emit('get-message', messageObj)
} )

In this you see we are using “io.emit”, instead of “socket.emit” that we did on the client side. This is because each client socket is connected to the server side socket, they are not interconnected directly. If we were to use “socket.on” it would work for only that particular socket whose connection is, i.e. the sender. Using “io.emit” will emit the event to all the sockets that are connected to it. So we need to register a client side listener for “get-message”.
We need to add this listener to when the connection is created, ie on the createConnection() function.
File : /client/index.js

async function createConnection(){
    socket = io( "http://localhost:3090", { query: { name: username.value } } )
    // or just//
    // socket = io('http://localhost:3090') // it takes the URL to the socket server.
 
    socket.on( 'get-message', async function(messageObj){
        console.log('got message : ', messageObj )
    } )
}

Here we have added a console to check if the console is printed on every browser that is connected.

But our goal is to display the message on the browser and not on the console so lets create a function to handle that as well.
File : client/index.js

function receiveMessage(messageObj){
    let pElement = document.createElement('p')
    let boldElement = document.createElement('b') //for name
    boldElement.innerHTML = messageObj.username + " : "
 
    let spanElement = document.createElement('span') // for message text
    spanElement.innerHTML = messageObj.message
    pElement.appendChild(boldElement);
    pElement.appendChild(spanElement);
    charArea.appendChild(pElement);
}

Lets also update the registered event for “get-message”., so that this receiveMessage() function is called whenever a message is received.
File : /client/index.js

socket.on( 'get-message', async function(messageObj){
        receiveMessage(messageObj)
} )

And that’s it.

Okay now let’s check the structure of the files and the code inside them.

project/
    client/
        index.html
        index.js
    server/
        node_modules/
        package-lock.json
        package.json
        socket-server.js
        static-server.js

/client/index.html

<!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">
    <title>Simple Chat App</title>
    <script src="https://cdn.socket.io/4.5.0/socket.io.min.js" integrity="sha384-7EyYLQZgWBi67fBtVxw60/OWl1kjsfrPFcaU0pp0nAh+i8FD068QogUvg85Ewy1k" crossorigin="anonymous"></script>
</head>
<body>
    <div id="loginForm">
        <input type="text" id="username">
        <button type="button" id="setNameBtn">Set Name</button>
    </div>
    <button id="disconnectBtn" hidden>Disconnect</button>
 
    <div>
        <h1>Chat Area</h1>
        <input type="text" id="msgBox" hidden>
        <button type="button" id="sendBtn" hidden>Send</button>
        <div id="charArea" hidden> </div>
    </div>
</body>
<script src="./index.js"></script>
</html>

/client/index.js

let loginForm = document.getElementById('loginForm');
let loggedIn = false;
 
let username = document.getElementById('username');
let loginButton = document.getElementById('setNameBtn');
 
let disconnectBtn = document.getElementById('disconnectBtn');
 
let charArea = document.getElementById('charArea');
 
let msgBox = document.getElementById('msgBox')
let sendBtn = document.getElementById('sendBtn')

function login(){
    if (!username.value || loggedIn) return;
 
    loggedIn = true;
    loginForm.setAttribute('hidden', true)
    disconnectBtn.removeAttribute('hidden')
    charArea.removeAttribute('hidden')
    msgBox.removeAttribute('hidden')
    sendBtn.removeAttribute('hidden')

    createConnection()
}
loginButton.onclick = login;
 
function logout(){
    if (!loggedIn) return;
   
    loginForm.removeAttribute('hidden');
    charArea.setAttribute('hidden', true)
    charArea.innerHTML=""
    disconnectBtn.setAttribute('hidden', true)
    msgBox.setAttribute('hidden', true)
    sendBtn.setAttribute('hidden', true)
    loggedIn = false;
    destroyConnection()
}
disconnectBtn.onclick = logout;

async function createConnection(){
    socket = io('http://localhost:3090', { query: { name: username.value } } )
    // or just//
    // socket = io('http://localhost:3090') // it takes the URL to the socket server.
     
    socket.on( 'get-message', async function(messageObj){
        receiveMessage(messageObj)
    } )

}

 
function destroyConnection(){
    socket.disconnect()
}

function sendMessage(){
    if (msgBox.value){
        socket.emit( 'send-message', {
            username: username.value,
            message : msgBox.value
        })
        msgBox.value = "" //to clear the message box
    }
};
sendBtn.onclick = sendMessage

function receiveMessage(messageObj){
    let pElement = document.createElement('p')
    let boldElement = document.createElement('b') //for name
    boldElement.innerHTML = messageObj.username + " : "
 
    let spanElement = document.createElement('span') // for message text
    spanElement.innerHTML = messageObj.message
    pElement.appendChild(boldElement);
    pElement.appendChild(spanElement);
    charArea.appendChild(pElement);
}

/server/static-server.js

const express = require('express');
const app = express();
const port = 3030;
 
app.use('/', express.static( '../client') )
 
app.listen( port, ()=>{
    console.log(`STATIC server http://localhost:${port}`)
} )

/server/socket-server.js

const app = require('express')();
const PORT = 3090;
const httpServer = require('http').createServer(app);
const io = require('socket.io')(httpServer, { cors: { origin:"*" } })

httpServer.listen( PORT, async ()=>{
    console.log('SOCKET listening on port :', PORT)
} )
 
io.on('connection', async (socket)=>{
    console.log('socket connection successful ', socket.id)
    
    socket.on('disconnect', async function(){
        console.log('socket disconnected : ', socket.id)
    })
    // listening to "send-message" event from the client side
    socket.on( 'send-message', async function( messageObj ){
    // socket.emit('get-message', {...msgData, route: 'emit'}) 
    // to self
    // io.to('pass_any_socketId'>).emit('message', msgData) 
    // to a particular socket id
    // socket.broadcast.emit('get-message', msgData) 
    // to every socket except the sender
    io.emit('get-message', messageObj) // to everyone including self
    } )
  } )

Visit our GitHub’s simpleChatApp repository for the source code.
https://github.com/agamitechnologies/Demo/tree/main/simpleChatApp

Thanks😎