Real-Time Communication with Socket.io: Building Interactive UIs

Introduction

In modern web applications, real-time and bi-directional communication plays a crucial role in delivering a dynamic and interactive user experience. Whether it’s a chat application, a collaborative document editor, a live dashboard, or an interactive filter, having instant updates is a must. This is where socket.io, a powerful library for Node.js and other languages, comes into play. In this blog, we’ll explore how socket.io can be used to facilitate real-time communication between a Node.js backend and a frontend UI.

What is Socket.io?

Socket.io is a library that enables real-time, bi-directional communication between web clients and servers. It allows for instant and flexible data exchange, providing a seamless user experience where changes are reflected in real-time without the need for manual page refreshes. Socket.io works on top of the WebSocket protocol but also provides fallback options for older browsers that don’t support WebSocket.

Setting up Socket.io in Node.js

To get started, ensure that you have Node.js installed on your system. In our discussion, we will serve the frontend HTML using another popular library called express. Run the following commands in your Node.js project directory to initialize the project and install express and socket.io:

npm init
npm install express socket.io

The npm init command is interactive and will result in creating a package.json file that holds some project metadata. The second command will install the specified packages (and their dependencies) and add these dependencies to package.json.

Once installed, you can require the express and socket.io modules in your Node.js server code, and start the relevant objects as follows:

const express=require('express');
const app=express();
const http=require('http');
const server=http.createServer(app);
const {Server}=require('socket.io');
const io=new Server(server);

server.listen(nPort,()=>{
    console.log('Listening on port '+nPort)
});

With express and socket.io set up on the server side, we can now focus on integrating it with our frontend UI.

Using Socket.io in the Frontend UI

To utilize socket.io on the frontend, we need to include the socket.io client library in our HTML file. You can use a CDN or install it locally using npm, but modern socket.io versions can also serve it directly from the server, as shown below:

<html>
<head>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket=io();
socket.on('connect',()=>{
    // Code to run on socket.io goes here
});
</script>
</head>
<body>
    <!-- Page body goes here -->
</body>
</html>

In the above code snippet, in line 3 we include the socket.io client library provided by the server. The connection to the server is initialized through the call to io() in line 5. With this connection, we can now send and receive real-time data. Data incoming from the server and connection events are handled using socket.on() functions. For example, see in line 6 the handler for the connection event. In the other direction, this client can send data to the server using socket.emit() calls.

Interactive Search Example

To demonstrate how socket.io can be used to update the UI in real-time, let’s implement a simple real-time search app that features a client-side search box where the user types some text. This text is communicated to the server using socket.io, and the server sends back any matches.

Let’s start with the server side. Instead of listing the moving parts piecemeal, I elected to show the whole thing and provide some explanations below. Copy this entire code and place it into a file called server.js:

server.js
const nPort=3000;
const express=require('express');
const app=express();
const http=require('http');
const server=http.createServer(app);
const {Server}=require('socket.io');
const io=new Server(server);

app.get('/',(req,res)=>{
    res.sendFile(__dirname+'/client.html');
});

io.on('connection',(oSocket)=>{
    console.log('client connected');
    oSocket.on('disconnect',()=>{
	// Handle client disconnect
    });
    oSocket.on('other',(oArgs)=>{
        console.log(`client command '${oArgs.cmd}'`);
        if (oArgs.cmd=='search') {
            oSocket.emit('other',{cmd:'search_results',data:SearchData(oArgs.text)})
        }
    });	
});

server.listen(nPort,()=>{
    console.log('Listening on port '+nPort)
});

function SearchData(sText) {
    var oResults={};
    if (sText) {
        sText=sText.toLowerCase();
        for (var sData in aoData) {
        if ((sData.includes(sText)) ||
            (aoData[sData].includes(sText)))
            oResults[sData]=aoData[sData];
        }
    }
    return(oResults);
}
const aoData={
    usa: 'Washington DC',
    japan: 'Tokyo',
    australia: 'Canberra',
    brazil: 'Brasilia',
}

So what do we have here? Lines 1 through 7 should look familiar – this is the initialization sequence to get express and socket.io going. The app.get() function in line 9 serves the client.html file to our browser when we load the page (more on express in another blog). And in line 26 we start the server on the specified port 3000.

The io.on() function in line 13 is where the fun begins – this is the function that will be called when a client is attempting to connect. When that happens, we register two other event handlers – a “disconnect” handler (that doesn’t do much here), and a “other” handler. The “other” event is arbitrary, and I chose it just to demonstrate the flexibility of the socket.io library. We can use any name for our events (other than the reserved event names like “connection” and “disconnect”), and we can send any type of data over the connection (in this case, we send a simple object).

So when the client sends a “other” event with data that looks like {cmd:"search",text:"usa"}, the server will call the function SearchData(), passing it the string “usa” as an argument, and this function will search the data object aoData for matches. The matches will be returned to the “other” handler and sent back to the client using a an emit() call.

Simple so far, right? Now let’s shift our attention to the client code to see what’s happing inside our browser. Copy the following code into another file called client.html:

client.html
<html>
<head>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket=io();
socket.on('connect',()=>{
    console.log(`Client connected`);
    var oEt=document.getElementById('search');
    oEt.focus();
});
socket.on('connect_error',(oArgs)=>{
    if (oArgs.type=='TransportError') return;
    console.log(`Client error ${oArgs.type}, ${oArgs.message}`);
});
socket.on('disconnect',()=>{
    console.log(`Client disconnected`);
});
socket.on('other',(oArgs)=>{
    console.log(`Socket command '${oArgs.cmd}', '${JSON.stringify(oArgs).substring(0,100)}'`);
    if (oArgs.cmd=='search_results')
        SearchResults(oArgs.data);
});
function Search() {
    var oEt=document.getElementById('search');
    var sText=oEt.value;
    if (!socket?.connected) {
        var oEt=document.getElementById('search-results');
        oEt.innerHTML='No connection to server, try again later';
    }
    else
        socket.emit('other',{cmd:'search',text:sText});
}
function SearchResults(aoData) {
    var sHtml='';
    var nIx=1;
    for (var sId in aoData) {
        sHtml+=`<tr>`;
        sHtml+=`<td class='search-ix'>${nIx++}.</td>`;
        sHtml+=`<td class='search-id'>${sId}</td>`;
        sHtml+=`<td class='search-name'>${aoData[sId]}</td>`;
        sHtml+='</tr>';
    }
    if (!sHtml) sHtml='No data found';
    var oEt=document.getElementById('search-results');
    oEt.innerHTML=sHtml;
}
</script>
<style>
body {
    margin: 0;
}
#top-frame {
    height: 40px;
    margin-bottom: 20px;
    border-bottom: 1px solid #bbbbbb;
    background-color: #ffe386;
    padding: 10px;
}
#search-frame {
    text-align: center;
    width: 100%;
}
#search {
    height: 40px;
    width: 60%;
    font-size: 26px;
    border: 1px solid #bbbbbb;
    padding-left: 5px;
    padding-right: 5px;
}
#results-frame {
    padding: 10px;
}
table {
    border-collapse: collapse;
}
tr:hover {
    background-color: #ffedb0;
}
td {
    border: 1px solid #bbbbbb;
    padding-left: 5px;
}
.search-ix {
    width: 45px;
}
.search-id {
    width: 80px;
    font-weight: 600;
}
.search-name {
    width: 400px;
}
</style>
</head>
<body>
<div id="content-frame">
    <div id="top-frame">
        <div id="search-frame">
            <input id="search" placeholder="search" oninput="Search()">
        </div>
    </div>
    <div id="results-frame">
        <table id="search-results"></table>
    </div>
</div>
</body>
</html>

In this discussion, we will ignore the static HTML and CSS sections – they just implement a simple search box and a results div that will be dynamically populated when the results are coming in. Lines 3 through 10 should also look familiar – this is where we import the socket.io client library and connect to the server.

The more interesting code is in the socket.on() functions, and the Search() and SearchResults() functions. When we type into the search box, it calls the Search() function and this function calls the socket emit() function to send the “other” event with an object data that looks like {cmd:"search",text:"what we typed"}. This is sent to the server, and as we explained above, the server will search for matches and send the results back, and the socket.on('other'...) function in line 18 will get this incoming data and call the SearchResults() function to show this data in the HTML table.

With these two files created in the project directory with the code above, just run npm start to run the app, and point your browser to http://127.0.0.1:3000. This will load the client code and you try searching for “usa”, “japan”, “australia” or “brazil”. Neat, right?

And if you want to see a real-world implementation of this interactive search, see it in action at https://coderlanes.com/studio – this is the landing page for Coder Lane’s experiential playground where learners can quickly find a unit by typing its acronyms or partial name.

Summary

Socket.io is a powerful tool for implementing real-time communication in web applications. Its simplicity and versatility make it an ideal choice for building features like chat applications, collaborative editing, live searches and notifications, and more. By using socket.io in combination with Node.js on the backend and integrating it with frontend UI, developers can create dynamic and interactive apps that provide an engaging user experience. So go ahead and explore the possibilities of socket.io to enhance your Node.js applications with real-time capabilities!

Similar Posts