WebSocket IO with NodeJS — with a complete example

Navid Mostafiz
6 min readJul 3, 2020

What is WebSocket? Making a real-time chat app using Socket.IO with NodeJS? How easy can that be? We shall see!

A WebSocket facilitates Real-Time Bidirectional Communication between two or more clients through a connecting server. This is contrary to WebRTC, which allows direct Real-Time Bidirectional Communication between two or more clients without the need for a server at all times [meaning, once the server connects the clients, the clients can now communicate directly with each other.]. I am planning on writing on WebRTC next 😃

Understanding the benefits of WebSocket can be realized better when developing a chat app. First of all, sending HTTP requests over the network to facilitate active communication between clients would be very costly. Redundant header information would need to be sent over the network each and every time. While in the case of WebSocket connections, once a costly initial call establishes a connection between any 2 clients, now very small packages of data can be sent between each other. This can facilitate faster, lighter data passing and also continuous data passing or streams of data for audio/video, etc types of information.

BEWARE, PEOPLE! this article shows code snippets with a mix of Javascript ES5 and ES6 😛

How do we make a basic WebSocket app in NodeJS?
* A server app to establish communication between Clients. [NodeJS, NodeExpress, Socket.IO etc]
* Client-side app for listening and casting messages to its audience. [JS, HTML, CSS etc]

Suggested:
* Nodemon (for hot loading, for dev env only, install if you don’t already have it installed globally on your machine)

bash

npm i express
npm i socket.io
npm i -D nodemon //as dev dependency, use -g if desired

The server app skeleton:

server.js

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var PORT = process.env.PORT || 5000;
//WE WILL USE THIS SERVER APP TO SERVE THE CLIENT ALSO!!
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
// ALL WEBSOCKET CODE GOES HERE...http.listen(PORT, function () {
console.log('WebSocket App started on PORT: ' + PORT);
});

As you can see, we have loaded our Socket IO library here. Let’s use it to listen to any client who wants to connect to our app on a host and given port. Any client who will open WebSocket connection using our host and port will implicitly trigger the connect event ‘connection’ and be connected.

server.js

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var PORT = process.env.PORT || 5000;
// Listening to clients getting connected and disconnected
io.sockets.on('connection', function(socket) {
console.log("LOG: [EVENT=connection] New client connected.");
//A client left
socket.on('disconnect', function() {
console.log("LOG: [EVENT=disconnect] client has disconnected.");
});
});http.listen(PORT, function() {
console.log('WebSocket App started on PORT: ' + PORT);
});

The Client-side:
index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>terminal-x</title>
</head>
<body>
<!-- ALL HTML GOES HERE -->
<textarea id="outputID" row="0" readonly></textarea>
<input id="inputID" type="text" />
<!-- SCRIPT GOES HERE -->
<script src="/socket.io/socket.io.js"></script>
<script src="/client.js"></script>
</body>
</html>

client.js:

//SOCKET CONNECTION CHATROOM
var sock = io.connect();
//THIS IS WHERE WE WILL EVENTUALLY ADD CODES
//TO EMIT AND LISTEN FOR MESSAGES FOR EACH EVENT

🌹ROOMS!
For privacy and to facilitate group chats, we will use rooms. Rooms are individual channels created inside the same socket connection. A number of clients can join each channel/room and have separate group conversations.

🍓 The sample code below pretty much completes the server-side app for us:
As you can see, the first event we want to listen to an event ‘connection', once clients connect to our WebSocket, now we are listening for an event ‘join_room’. Every time a client wants to create a room with a new name, we create a room/channel for that. If a client wants to create a channel/room with an existing name, it is simply ignored. Duplication now allowed. A mechanism can be implemented to append a unique identifier each time clients try to open rooms/channels with the same name.

client.js

room_name = "room name I want to create and subscribe";
sock.emit('join_room', room_name);

server.js

// server side code: ROOM
io.sockets.on('connection', function(socket) {
socket.on('join_room', function(room_name) {
socket.join(room_name);
console.log(socket.rooms); //all rooms created so far
});
});

Listening to rooms: From the server-side, we are listening to an event ‘message_to_server’, this event can be triggered by client-side from any room. Then we will broadcast that message to every client in that room. Now, how do we know which room it came from? We will add that room name inside the data by the client itself. As you can see in the code below

server.js

//LISTEN TO ROOMS
socket.on('message_to_server', function({
room_name,
from,
msg
}) {
//we will emit this message to all clients in given room
});

Emitting to rooms: We got this from client-side, telling use, what is the room, who the user is, and what the message is. We can now emit this message

server.js

//LISTEN TO ROOMS AND EMITTING TO ALL CLIENTS IN THAT ROOM
socket.on('message_to_server', function({
room_name,
from,
msg
}) {
//we will emit this message to all clients in given room
io.in(room_name).emit('message_to_client', {
from,
msg
});
});

client.js

// EMITTING MESSAGES FROM CLIENT TO ROOM
var room_name = "name of room to which we will emit"
var from = "could be username, some way to identifiy who sent it"
var msg = "the message from this client"
sock.emit('message_to_server', { room_name, from: username, msg });

The code above will be triggered by the user from client UI, using the event ‘message_to_server’, we are adding a username and room name in the payload, so the server-side can know which room this message should be sent out to.

Here’s, the complete code with comments! let’s study the self-explanatory complete code sample.

server.js

"use strict";var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var PORT = process.env.PORT || 5000;
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
// server side code: ROOM
io.sockets.on('connection', function(socket) {
console.log("LOG: [EVENT=connection] New client connected.");
//EACH TIME WE WANT TO CREATE A NEW ROOM FROM CLEINT SIDE, EVENT = CREATE
socket.on('join_room', function(room_name) {
console.log("LOG: [EVENT=join_room] [room_name=" + room_name + "]");
console.log(socket.rooms);
socket.join(room_name);
});
//LISTEN TO ROOMS
socket.on('message_to_server', function({
room_name,
from,
msg
}) {
//we will emit this message to all clients in given room
io.in(room_name).emit('message_to_client', {
from,
msg
});
});
//A user disconnected from room
socket.on('disconnect', function() {
console.log("LOG: [EVENT=disconnect] A client has disconnected.");
});
});http.listen(PORT, function() {
console.log('WebSocket App started on PORT: ' + PORT);
});

Let's take a look at client-side script. Most of the code is static, for easy understanding. It can be made dynamic and event-triggered after basic understanding.

client.js

//SOCKET CONNECTION CHATROOM
var sock = io.connect();
var outputDOM = document.getElementById("outputID");
var inputDOM = document.getElementById("inputID");
var username = "";
var msg = "";
var room_name = "";
//+++++++++++++++++++++++++++++++++++++
// REQUEST SERVER TO CREATE A NEW ROOM:
//+++++++++++++++++++++++++++++++++++++
room_name = "some room name";
sock.emit('join_room', room_name);
//+++++++++++++++++++++++++++++++++++++
// LISTENER TO SERVER:
//+++++++++++++++++++++++++++++++++++++
sock.on('message_to_client', function({
from,
msg
}) {
// ******
outputDOM.innerHTML += "</br>" + decryptedMSG(data);
outputDOM.rows += 1;
// ******
});
//+++++++++++++++++++++++++++++++++++++
// SEND TO SERVER:
//+++++++++++++++++++++++++++++++++++++
function sendToServer(msg, room_name) {
sock.emit('message_to_server', {
room_name,
from: username,
msg
});
}
//USER GAVE INPUT: SEND TO SERVER
inputDOM.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
//+++++++++++++++++++++++++++++++++++++
if (inputDOM.value != "") {
//SEND TO SERVER
sendToServer(inputDOM.value, token);
//RESET INPUT TERMINAL
inputDOM.value = ""
} else {
inputDOM.placeholder = "empty message cant be sent!";
}
//+++++++++++++++++++++++++++++++++++++
}
});

If there is still any confusion please take a look at the complete project on WebSocket using rooms. Clone it and follow instructions in readme.md. It is not a barebone boilerplate for the Socket app but should be simple enough to understand.
LINK: A complete sample project written with Socket.IO in nodeJS

The given project can be deployed into Heroku easily. Two shell scripts have been added. Enjoy!! please leave comments or claps!!

--

--