DEV Community

Cover image for WebSockets and NodeJs: Real time chat app
alakkadshaw
alakkadshaw

Posted on • Originally published at deadsimplechat.com

WebSockets and NodeJs: Real time chat app

_Note: This article was originally written on the DeadSimpleChat blog: WebSockets and NodeJs: Real time chat app _

Websockets are a communication protocol that provides a full-duplex communication channels over a single, long-held TCP connection.

Using web sockets you can create a persistent, two way interaction between a client and a server.

In this article we are going to learn the following, you can click on a click to move to that particular section of the article

  • What are WebSockets?
  • HTTP req-res Model vs Websockets
  • Real World Advantages and use-cases of Websockets
  • Why NodeJs is the recommended platform for using WebSockets?
  • Step by Step Tutorial Building a real time Chat App with WebSockets and Node JS
  • Creating the backend
  • Step 1: Environment SetUp
  • Step 2: Installing Dependencies
  • Step 3: Creating the Back-end server
  • Testing the Back-end server
  • Building the front-end/Client side
  • Step 4: Creating the Public Directory
  • Step 5: Creating the UI of the Chat App
  • Step 6: Implementing the websocket on the client side
  • Bonus: Sending chat message to all connected clients

What are websockets?

as explained above, Websockets are a communication protocol that provides a full-duplex communication channels over a single, long-held TCP connection.

Here is how the websockets work

1. Establishing the connection:

To establish a websocket connection, a handshake between the client and server happens.

This handshake is initiated by the client and includes a special kind of HTTP header called and ' Upgrade' header

Which signifies that client wants to establish a websocket connection

2. Upgrading to WebSocket Protocol:

If the server supports the websocket connection then it replies with 'Upgrade' header in its response which signifies and confirms that the connection is swiched from HTTP to websocket

3. Persistent, two way communication:

Once the handshake is complete the connection is swicthed from HTTP to websocket connection.

which is a persistent two way communication channel where each party in the connection that is the client as well as the server can send data to each other any time

HTTP req-res Model vs Websockets

HTTP unique features

1. Stateless Protocol

HTTP is a stateless protocol, what it means is that it operates in a request response model

Each request from the client requires a seprate HTTP request and the server responses to that request and the connection is closed

for example: client request data to load a page, server response with the data and the connection is closed

2. One way Communication:

In HTTP the connection is unidirectional, client sends a request and the server recieves the request then responses to that request. This makes it less efficent in use cases where there is need for contineous exchange of data

3. Overhead

Each HTTP request contains headers and other meta data which makes a HTTP request heavy as compared to a websocket connection

WebSocket unique features

1. Full-Duplex communication

Websockets allow for full duplex communication, which means that the data can go through any direction that allows for real time interaction

2. Reduced Overhead

Websockets do not contain additional meta data like HTTP headers which reduces data overload and improves performance especially over slow networks and allows for smooth flow of data

3. Stateful Protocol

Unlike HTTP, the websocket protocol is stateful, which means the server and client knows that they are connected to each other and the connection remains open until it is closed

Real World Advantages and use-cases of Websockets

Here are some of the real world applications and advantages of WebSockets.

Real world use cases and apps

  1. Chat Application: Real time chat app can be easily created using websockets
  2. Gaming: Online gaming where fast bi directional data transfer is required to play games. Especially fast paced games
  3. Financial trading platforms: Financial trading platforms where real time fast data transfers is required such as stock prices and fat execution of trades
  4. Live Sports Updates: Sporting events are good and drives real time data transfer is neessory for the viewer to be engaged in sports
  5. Collaboration Tools: Real time collaboration tools such as whiteboard and document sharing require websockets to function
  6. Notification Services: Instant notification and alerts can be made using websockets

Advantages

  1. Real Time interaction
  2. Efficient Performance
  3. Scalability
  4. Flexibility

What is Node Js and why NodeJs is the recommended platform for using WebSockets?

Node JS is an open source cross platform JavaScript runtime environment, using which you can run JavaScript on the server side.

Node JS is built on the V8 runtime environment of Chrome that allows developers to use JavaScript outiside the browser

  1. JavaScript everywhere: NodeJS extends javascript to the server as well. Before javascript was only used inside the browser
  2. Event-Driven architecture: Node JS has an event driven architecture and non blocking I/O based model that makes it lightweight for your applications
  3. NPM Node Version Manager: Node Js comes with an enourmous library of packages that are managed through NPM. NPM makes it easy to incorporate various tools and packages into your application
  4. Async and Non Blocking: Node JS is Async and non blocking by nature, which means it can continue to process other requests while waiting for the completion of tasks like reading a file etc
  5. Scalable: Node Js has a lightweight arhitecture and can scale to thousands of connections

Why Node Js is preferred to be used with WebSockets

  1. Handling concurrent connections
  2. Real Time performance
  3. Unified Javascript Development
  4. Vast Ecosystem and Community Support
  5. Efficient handling of Binary Data
  6. Easy integration with Web technologies

Step By Step Tutorial: Building a Real time chat app

In this section we are going to build a real time chat application in a step by step format

  • Environment setup:
  • Creating the server with NodeJs and WebSockets
  • Creating the client
  • Handling client server communication
  • Running the app
  • Optional Enhancing the Application
  • User Auth
  • Storing messages
  • Adding rooms and channels
  • UI improvements

Creating the backend

Step 1: Environment Set up

Create a new project directory in your machine and and name it real-time-chat-app and cd into it like so

mkdir real-time-chat-app
cd real-time-chat-app
Enter fullscreen mode Exit fullscreen mode

then initalize the node js project with npm init to create a package.json file

npm init
Enter fullscreen mode Exit fullscreen mode

Step 2: Installing Dependencies

next step is to install the dependencies for our project. We will be needing the express js and the ws websocket library to set up the server for our real time chat application

install the express js like

npm install express
Enter fullscreen mode Exit fullscreen mode

install the ws websocket library like

npm install ws
Enter fullscreen mode Exit fullscreen mode

Step 3: Creating the Back-End servers

Create a index.js file in your project then write the below code to setup an express js server

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

This is a simple express js server which listens on the port 3000 and returns the hello world of the / endpoint

next step is to add the ws library to the index.js file and setup the websocket server running independently on some other port

Add the below code to the index.js file:

const WebSocket = require('ws');

// We are creating a new websocket server here and running it on port 8181
const wss = new WebSocket.Server({ port: 8181 });

wss.on('connection', function connection(ws) {
    ws.on('message', function incoming(message) {
        console.log('received: %s', message);
    });

    ws.send('This is a message');
});
Enter fullscreen mode Exit fullscreen mode

What are we doing in this code

  1. We are importing the websocket library ws
  2. we are running the websocket server on port 8181. This server is running independently of the HTTP expressjs server which means that it does not share the same port or connection as the HTTP server
  3. We are handling the websocket connections
wss.on('connection', function connection(ws) {
 //.....
});
Enter fullscreen mode Exit fullscreen mode

here we are listning for new websocket connections. When a client connects to the server via websockets the callback method is triggered

the ws parameter here represents the connected websocket client.

  1. Then inside the callback function we are setting up another listener that listens to any messages that the client is sending to the server. Whenever a message is recieved we are logging that message to the console for now. Later we can send this message back to the client or do whatever we want to do with the message
ws.on('message', function incoming(message) {
    console.log('received: %s', message);
});
Enter fullscreen mode Exit fullscreen mode
  1. Lastly, we are sending a sample message back to the client.
 ws.send('This is a message');
Enter fullscreen mode Exit fullscreen mode

Testing the Back-end server

Now that we have created our simple express js and websocket servers. We can test them.

It is quite easy to test these servers, we are going to use third party tools such as Postman to do this.

Testing the express js server

paste the localhost:300 and send a GET request to the expressjs server and you will get a hello world response

Testing the server

Testing the websocket server

to create a websocket request on the postman sidebar click new and then click on websocket

then paste the websocket server url and create a connection ws://localhost:8181

click on the connect button to connect to the websocket server and type a message in the message section and click on the send button to send the message

You can see the connection established in the postman response section also the message that was sent from the websocket server.

Also, you can see the message logged to the server on the server console log

Image description

Image description

Here is how the complete back-end code looks like

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

const WebSocket = require('ws');

// Create a WebSocket server completely detached from the HTTP server.
const wss = new WebSocket.Server({ port: 8181 });

wss.on('connection', function connection(ws) {
    console.log("WS connection arrived");
    ws.on('message', function incoming(message) {
        console.log('received: %s', message);
    });

    ws.send('this is a message');
});


app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Now we have created a simple server and tested the basic functionality. Our server doesn't do much except send a basic message back to the client.

We want the server to take the messages from a single client and send it back again to the sender client.

Let us edit the server code to add that functionality.

const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 3000;

// Serve static files from a 'public' directory
app.use(express.static(path.join(__dirname, 'public')));

// Create HTTP server by passing the Express app
const server = http.createServer(app);

// Integrate WebSocket with the HTTP server
const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws) {
    console.log("WS connection arrived");

    ws.on('message', function incoming(message) {
        console.log('received: %s', message);
        // Echo the message back to the client
        ws.send(`Echo: ${message}`);
    });

    // Send a welcome message on new connection
    ws.send('Welcome to the chat!');
});

// Default route can be removed if you are serving only static files
// app.get('/', (req, res) => {
//     res.send('Hello World!');
// });

// Start the server
server.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

What are we doing here

  1. we have edited the express code to send files from the public directory. We are going to build the front-end of our chat app and send the html and js files for the front end from the public directory
  2. In the websocket we are sending the message that we are recieving back to the client
  3. We can also broadcast the message to all the client connected to the websocket server. We are going to do this later on in the article as a bonus content

Building the front end / Client side

Next we are going to be building the front end of our real time chat application.

Step 4 : Creating the Public Directory

In the root folder of your application create a new directory called the public directory

there create two new files

  • index.html: Here we will write the UI of the chat app
  • app.js : Here we will write the front end logic of the chat app

Step 5 : Create the UI of the chat app

Open the index.html file and paste the following code in it.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat App</title>
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.1/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
    <div class="container mx-auto p-4">
        <h2 class="text-2xl font-bold mb-2">Real-Time Chat</h2>
        <div id="messages" class="bg-white p-4 h-64 overflow-auto mb-4"></div>
        <input type="text" id="messageInput" class="border p-2 w-full">
        <button id="sendButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
            Send
        </button>
    </div>
    <script src="app.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

this is basically creating a chat interface and styling it with tailwind css. You can open the file in the browser and it looks something like this

Image description

Step 6 : Implementing websocket on the client side

Next we are going to create a javascript file and name it app.js on the client side

const ws = new WebSocket(`ws://${window.location.host}`);
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');

ws.onopen = () => {
    console.log('Connected to the server');
};

ws.onmessage = (event) => {
    const message = document.createElement('div');
    message.textContent = event.data;
    messages.appendChild(message);
};

ws.onerror = (error) => {
    console.error('WebSocket error:', error);
};

ws.onclose = () => {
    console.log('Disconnected from the server');
};

sendButton.onclick = () => {
    const message = messageInput.value;
    ws.send(message);
    messageInput.value = '';
};
Enter fullscreen mode Exit fullscreen mode

This file handles the connection on the client side, receiving messages and sending messages clearing the message box updating the HTML interface.

Image description

If you go to the Localhost://3000 you can see the chat interface and if you send the message you can see it in the chat box

DeadSimpleChat

Need JavaScript Chat API for your website or app

DeadSimpleChat is an Chat API provider

  • Add Scalable Chat to your app in minutes
  • 10 Million Online Concurrent users
  • 99.999% Uptime
  • Moderation features
  • 1-1 Chat
  • Group Chat
  • Fully Customizable
  • Chat API and SDK
  • Pre-Built Chat

Bonus: Sending chat message to all the connected clients

In this section we are going to change the server code to send the message to all the connected clients instead of just the sender.

for this first we need to create an arry to store all the clients that are currently connected to the websocket server

// Array to store all the connected clients
const clients = [];
Enter fullscreen mode Exit fullscreen mode

then edit the websocket code to

  1. add a client to the array whenever a new client is connected
wss.on('connection', function connection(ws) {
    console.log("WS connection arrived");

    // Add the new connection to our list of clients
    clients.push(ws);
    //...
Enter fullscreen mode Exit fullscreen mode
  1. Broadcast the message to all the connected clients
    ws.on('message', function incoming(message) {
        console.log('received: %s', message);

        // Broadcast the message to all clients
        clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(message.toString());
            }
        });
    });
Enter fullscreen mode Exit fullscreen mode
  1. when the connection closes remove the client from the clients Array
    ws.on('close', () => {
        // Remove the client from the array when it disconnects
        const index = clients.indexOf(ws);
        if (index > -1) {
            clients.splice(index, 1);
        }
    });
Enter fullscreen mode Exit fullscreen mode

here is how the complete server code looks like:

const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 3000;

// Serve static files from a 'public' directory
app.use(express.static(path.join(__dirname, 'public')));

// Create HTTP server by passing the Express app
const server = http.createServer(app);

// Integrate WebSocket with the HTTP server
const wss = new WebSocket.Server({ server });

// Array to keep track of all connected clients
const clients = [];

wss.on('connection', function connection(ws) {
    console.log("WS connection arrived");

    // Add the new connection to our list of clients
    clients.push(ws);

    ws.on('message', function incoming(message) {
        console.log('received: %s', message);

        // Broadcast the message to all clients
        clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                console.log("message",message.toString())
                client.send(message.toString());
            }
        });
    });

    ws.on('close', () => {
        // Remove the client from the array when it disconnects
        const index = clients.indexOf(ws);
        if (index > -1) {
            clients.splice(index, 1);
        }
    });

    // Send a welcome message on new connection
    ws.send('Welcome to the chat!');
});

// Start the server
server.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Image description

Source Code

index.js


const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');

const app = express();
const PORT = process.env.PORT || 3000;

// Serve static files from a 'public' directory
app.use(express.static(path.join(__dirname, 'public')));

// Create HTTP server by passing the Express app
const server = http.createServer(app);

// Integrate WebSocket with the HTTP server
const wss = new WebSocket.Server({ server });

// Array to keep track of all connected clients
const clients = [];

wss.on('connection', function connection(ws) {
    console.log("WS connection arrived");

    // Add the new connection to our list of clients
    clients.push(ws);

    ws.on('message', function incoming(message) {
        console.log('received: %s', message);

        // Broadcast the message to all clients
        clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                console.log("message",message.toString())
                client.send(message.toString());
            }
        });
    });

    ws.on('close', () => {
        // Remove the client from the array when it disconnects
        const index = clients.indexOf(ws);
        if (index > -1) {
            clients.splice(index, 1);
        }
    });

    // Send a welcome message on new connection
    ws.send('Welcome to the chat!');
});

// Start the server
server.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat App</title>
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.0.1/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
    <div class="container mx-auto p-4">
        <h2 class="text-2xl font-bold mb-2">Real-Time Chat</h2>
        <div id="messages" class="bg-white p-4 h-64 overflow-auto mb-4"></div>
        <input type="text" id="messageInput" class="border p-2 w-full">
        <button id="sendButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
            Send
        </button>
    </div>
    <script src="app.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

app.js

const ws = new WebSocket(`ws://${window.location.host}`);
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');

ws.onopen = () => {
    console.log('Connected to the server');
};

ws.onmessage = (event) => {
    const message = document.createElement('div');
    message.textContent = event.data;
    messages.appendChild(message);
};

ws.onerror = (error) => {
    console.error('WebSocket error:', error);
};

ws.onclose = () => {
    console.log('Disconnected from the server');
};

sendButton.onclick = () => {
    const message = messageInput.value;
    ws.send(message);
    messageInput.value = '';
};
Enter fullscreen mode Exit fullscreen mode

Metered TURN servers

  1. Global Geo-Location targeting: Automatically directs traffic to the nearest servers, for lowest possible latency and highest quality performance. less than 50 ms latency anywhere around the world
  2. Servers in 12 Regions of the world: Toronto, Miami, San Francisco, Amsterdam, London, Frankfurt, Bangalore, Singapore,Sydney (Coming Soon: South Korea, Japan and Oman)
  3. Low Latency: less than 50 ms latency, anywhere across the world.
  4. Cost-Effective: pay-as-you-go pricing with bandwidth and volume discounts available.
  5. Easy Administration: Get usage logs, emails when accounts reach threshold limits, billing records and email and phone support.
  6. Standards Compliant: Conforms to RFCs 5389, 5769, 5780, 5766, 6062, 6156, 5245, 5768, 6336, 6544, 5928 over UDP, TCP, TLS, and DTLS.
  7. Multi‑Tenancy: Create multiple credentials and separate the usage by customer, or different apps. Get Usage logs, billing records and threshold alerts.
  8. Enterprise Reliability: 99.999% Uptime with SLA.
  9. Enterprise Scale: With no limit on concurrent traffic or total traffic. Metered TURN Servers provide Enterprise Scalability
  10. 50 GB/mo Free: Get 50 GB every month free TURN server usage with the Free Plan
  11. Runs on port 80 and 443
  12. Support TURNS + SSL to allow connections through deep packet inspection firewalls.
  13. Support STUN
  14. Supports both TCP and UDP

Top comments (1)

Collapse
 
alakkadshaw profile image
alakkadshaw

Thank you for reading. I hope you liked the article