Setting up Scalable WebSockets - WebSockets for real-time communication with Node.js
Setting up Scalable WebSockets - WebSockets for real-time communication with Node.js
A WebSocket is web protocol that facilitates bi-directional communication between clients and servers. In addition, WebSocket can send messages in either direction in near real-time, unlike HTTP.
This article discusses the importance of WebSockets and how to implement it using Node.js.
Why WebSockets?
You can use WebSockets whenever a low-latency, near real-time connection between the client and the server, is required. A few examples are as follows.
- Multiplayer gaming.
- Instant messaging applications.
- Voice/video media exchange.
- Live sports scores.
However, keep in mind that this may influence the architecture of your web applications.
One of the major changes comes with the scalability of your server. Since WebSockets use a constant connection, the load balancers need to keep an open connection with the client.
To ensure that the servers can scale out, you need to set up an external storage solution like Redis, to store client connection information and keep track of the messages delivered. It allows, for fault-tolerance, where the client can reconnect to a different server instance if one goes down.
WebSockets with Node.js
All recent web browsers come with a Websocket API. Therefore using WebSockets in the frontend is relatively simple. However, a backend application is required to utilize them on the server. This is where Node.js comes in.
Node.js can handle hundreds of WebSocket connections simultaneously. As the connection upgrades from HTTP to WebSockets on the server might become problematic as it requires handling. Most developers utilize a library to take care of this. Three popular WebSocket server frameworks make administering WebSockets easier:
- WS.
- SockJS.
- Socket.io.
Here, I will walk you through the step-by-step implementation of Socket.io with Node.js. The key concepts of WebSockets remain the same regardless of the library we use.
Socket.io
Socket.io is a reliable, scalable, and performant library that supports WebSocket's real-time duplex communication(allowing the transmission of two signals simultaneously in opposite directions).
- Multiple modes of transportation are supported. If the client supports WebSocket, it switches the connection from HTTP to WebSocket. If not, the message exchange is handled through HTTP long polling.
- If the backend stops responding, it immediately tests the connection and reconnects.
- Socket.io provides load balancing(broadcasting to all clients or to a subset of clients).
It would help if you considered these aspects before releasing a project using Node.js and WebSockets.
- Security
- Scalability
- Broken connections
- Refresh connections
- Automatic reconnection
- Monitoring
Implementing WebSockets with Node.js
I will use the simplest chat application implementation for this tutorial so that you will understand the steps clearly.
Getting started
To get started with Socket.io you need to have npm and Node installed in your machine. If not go to Node.js downloads and follow the instructions.
Step 1. Setting up the Environment
First, we need to set up a Node project:
Open your command prompt and run the following code. I have used Test as the name of the application.
mkdir Test
cd Test
npm init
Insert the entry point as server.js
and you may skip the rest of the questions. The package.json
file will be created inside the directory after the confirmation.
Step 2. Installing Socket.io
Install Socket.io
along with Express
using npm. This will install socket.io into our node project.
npm install --save socket.io express
We also need to install the client library for SocketIO.
npm i socket.io-client
Instead of using node app.js
, use nodemon app.js
whenever you need to start the server. This means you won’t have to restart the server every time you make a file change. This will help speed up the development process through live reloading.
npm i -g nodemon
We’ve now built up our development environment. Let’s get started with the server-side.
Step 3. Building the server-side
Create a server.js file inside the folder containing package.json
. We will use the server.js
file to set up our backend.
const application = require('express')(); const server = require('http').createServer(application) const io = require('socket.io')(server); const PORT = process.env.PORT || 3000 application.get('/', (req, res) => { res.sendFile(__dirname + '/index.html'); }); server.listen(PORT, () => { console.log('Server is running on port: ' + PORT); }); io.on('connection', (socket) => { socket.on('disconnect', () => { console.log('User is disconnected - Username: ' + socket.username); }); socket.on('new message', (msg) => { io.emit('send message', {message: msg, user: socket.username}); }); socket.on('new user', (usr) => { socket.username = usr; console.log('User is connected - Username: ' + socket.username); }); });
In the server.js
file we will:
- Initialize a new instance of express and create a new instance of
socket.io
, and pass it into our express instance. - Initialize port 3000, for the server to listen.
- Define a route to the client-side
index.html
file. - Then we set the
socket.io
to start listening for connection events and handle a disconnect event for each socket. - Uses and emit method to handle a new message event that sends all objects to the server. The message, as well as the sender’s username, is stored in these objects.
- When a client enters their username, the server receives it as a new user event and produces a connection message along with the connected client user’s username.
The require('socket.io')(http)
command generates a new socket.io instance connected to the HTTP server. The socket object is used by the io.on
event handler to handle connection, disconnection, and other events.
Up to now, we have finished setting up our server-side, next, we’ll move on to the client-side.
Step 4: Support for scaling
WebSockets needs a stateful connection between the client and the server. It becomes a challenge when we need to scale out.
To support this, socket.io
offers a storage solution to externalize the state of each instance running in a cluster. You can use the socket.io redis adapter to configure the scalability support.
Step 5. Building the client-side
Now, we need to create the index.html
file.
<!DOCTYPE html> <html> <head> <title>SocketsChat</title> <script src="/socket.io/socket.io.js"></script> <link rel="stylesheet" href="index.css"> </head> <body> <ul id="message_list"></ul> <form id="message_area" action=""> <input id="text_area" autocomplete="off" /> <button>Send</button> </form> <script> const socket = io(); const username = window.prompt("Enter the username"); socket.emit('new user', username); const messageForm = document.getElementById('message_area'); const textInput = document.getElementById('text_area'); messageForm.addEventListener('submit', function(e) { e.preventDefault(); if (textInput.value) {socket.emit('new message', textInput.value); textInput.value = ''; } }); socket.on('send message', (data) => { const messageList = document.getElementById('message_list'); const chatItem = document.createElement('li'); chatItem.textContent = data.user + ': ' + data.message; messageList.appendChild(chatItem); window.scrollTo(0, document.body.scrollHeight); }); </script> </body> </html>
Step 6. Run the App
Run the following command to check whether this works fine.
nodemon
The node application is now set up on express and serving an HTML file on the root. Socket.io will log “User connected” whenever someone visits this page, and “User disconnected” once someone leaves it.
If you go to *localhost:3000* right now (your server should be up and running), your browser will see a prompt to enter username.
WebSocket connections are now operational. This is how simple it is to use Socket.io to create connections.
Now open two windows to start using the chat application as two users to test our application.
Socket.io is built on events; you create events with their handlers, and when the event is triggered, the handler associated with it is executed.
When a connection is established, the connect event is triggered. The socket argument should be used to communicate with the client hereafter. When a client disconnects from the server, the disconnect event is triggered.
I think now you have a clear understanding of how to implement WebSockets with Node.js.
Pros and Cons of WebSockets
Pros
WebSockets replace HTTP with a streamlined bidirectional communication channel between client and server. The key advantages of WebSockets are as follows.
- Low latency - although there is a latency when opening a socket, data can be exchanged without any application layer overhead after that.
- Full duplex - bi-directional communication for client-server data transmission in real-time.
- Efficient and Scalable - supports large-scale communication and effective load balancing. Send and receive data immediately faster than HTTP and AJAX.
- Flexible - supports a variety of data types, including binary for voice and video media transmission and XML for messaging applications. It has cross-platform compatibility.
- Security - secure as it runs over a TLS(Transport Layer Security), which protects the transmitted data from eavesdroppers and hackers.
Cons
- No success functions like AJAX.
- WebSockets do not provide edge caching, unlike HTTP.
- HTTP is significantly easier to develop if your application does not require a lot of dynamic interaction.
- A web browser that is completely HTML5 compliant is necessary.
- TCP implementation is required for WebSockets, which may or may not be a problem, but it also requires an HTTP implementation for the initial connection setup.
Wrap up
WebSockets is your best alternative whenever you require a better low-latency connection between a client and a server.
WebSocket is a powerful and useful technique. Using it in Node.js is simple, and you can utilize a library that doesn’t require any additional packages in your frontend application because browsers already have it built-in.
Using it in Node.js is simple, and you can utilize a library that doesn’t require any additional packages in your frontend application because browsers already have it built-in.