How to Implement Real-Time Live Visitors Count Using Socket.io in Nest.js
Last time we implement a secure socket.io connection in nest js using jwt and password strategy authentication process this time we will dive into implementing how we can implement a real time live visitors in any web pages when user visit the web pages. These type of use cases is widly applicable in application like news portal site, blog portal sites or any other kinds of e-commerce ๐ ๐ such that you can use where you like. Okey lets dive in shall we!
For this you must need to have a basic understanfing of MAP in js no worries if you don't know i'll give a simple example where you can have a basic understanding of how MAP works and how does it store the data.
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
console.log(map.has('key1')); // Output: true
console.log(map.has('key3')); // Output: false
Since we are using TS we need to declare and initialize two private memebers variable with it's type.
private connectedClients = new Map<string, Set<Socket>>();
private blogClients = new Map<string, number>();
After successfully declaring and initializing the private instance variable the mail logics cames here. First review the code and i'll have you a basic understanding of how work flows.
@SubscribeMessage('blogLiveCounts')
handleBlogLiveCounts(
@ConnectedSocket() client: Socket,
@MessageBody() message: LiveBlog,
) {
const parsedMessage: LiveBlog = JSON.parse(`${message}`);
console.log('๐ ~ SocketGateway ~ jsonStringify:', parsedMessage.blog_id); //๐ ~ SocketGateway ~ jsonStringify: 2
if (!this.blogClients.has(client.id)) {
console.log('๐ ~ SocketGateway ~ client.id, :', client.id); //๐ ~ SocketGateway ~ client.id, : 7Fu4_qBToYPCBrlkAAAB
this.blogClients.set(client.id, parsedMessage.blog_id);
}
console.log('๐ ~ SocketGateway ~ this.blogClients:', this.blogClients); // example ๐ ~ SocketGateway ~ this.blogClients: Map(1) { '7Fu4_qBToYPCBrlkAAAB' => 2 }
// //getting all the values stored in blogClients
const allBlogClientsValues = Array.from(this.blogClients.values());
console.log(
'๐ ~ SocketGateway ~ allBlogClientsValues:',
allBlogClientsValues,
); ///example :- ๐ ~ SocketGateway ~ allBlogClientsValues: [ 2 ]
const filteredValues = allBlogClientsValues.filter((blog_id) => {
return blog_id === parsedMessage.blog_id;
});
console.log(
'๐ ~ SocketGateway ~ filteredValues ~ filteredValues:',
filteredValues,
filteredValues.length,
); //๐ ~ SocketGateway ~ filteredValues ~ filteredValues: [ 2 ] 1
this.server.emit('liveCountsSingleBlog', filteredValues.length);
}
So, in this piece of code, I've implemented a method called handleBlogLiveCounts
within my SocketGateway class. This method is triggered whenever an event named 'blogLiveCounts' is received.
First, I extract the message body, which presumably contains information about a live blog, such as its ID, title, and content. I parse this message into a LiveBlog
object.
Then, I extract the blog_id
from the parsed message and log it to the console. This helps me understand which blog the message is referring to.
Next, I check if the blogClients
map doesn't already have the client's ID stored. This check ensures that I'm not duplicating entries for the same client. If the client's ID isn't already in the map, I add it along with the blog_id
to the blogClients
map.
After adding the client's ID and blog_id
to the map, I log the entire blogClients
map to the console. This helps me keep track of which clients are currently connected and which blogs they're viewing.
Then, I retrieve all the blog_id
values stored in the blogClients
map. This step is crucial because it allows me to determine how many clients are currently viewing each blog.
Once I have all the blog_id
values, I filter them to find those that match the blog_id
from the parsed message. This filtering process helps me identify the clients that are viewing the same blog as the one mentioned in the message.
Finally, I emit an event named 'liveCountsSingleBlog' to the server. I pass the count of filtered values (i.e., the number of clients viewing the same blog) as the payload of this event. This allows other parts of the application to receive and act upon this information in real-time.
Below is the entire work of how we can implement the Live visitors count in any web pages using socket and Nest.js
import { UseGuards } from '@nestjs/common';
import {
ConnectedSocket,
MessageBody,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { JwtSocketGuard } from 'src/auth/guard/socket.guard';
interface LiveBlog {
blog_id: number;
user_id: number;
}
@UseGuards(JwtSocketGuard)
@WebSocketGateway(5000)
export class SocketGateway {
@WebSocketServer() server: Server;
private connectedClients = new Map<string, Set<Socket>>();
private blogClients = new Map<string, number>();
handleConnection(@ConnectedSocket() client: Socket) {
// Initialize the client's private room
console.log(
`๐ ~ SocketGateway ~ handleConnection ~ client.id: ${client.id} is connected`,
);
this.connectedClients.set(client.id, new Set());
}
@SubscribeMessage('blogLiveCounts')
handleBlogLiveCounts(
@ConnectedSocket() client: Socket,
@MessageBody() message: LiveBlog,
) {
const parsedMessage: LiveBlog = JSON.parse(`${message}`);
console.log('๐ ~ SocketGateway ~ jsonStringify:', parsedMessage.blog_id); //๐ ~ SocketGateway ~ jsonStringify: 2
if (!this.blogClients.has(client.id)) {
console.log('๐ ~ SocketGateway ~ client.id, :', client.id); //๐ ~ SocketGateway ~ client.id, : 7Fu4_qBToYPCBrlkAAAB
this.blogClients.set(client.id, parsedMessage.blog_id);
}
console.log('๐ ~ SocketGateway ~ this.blogClients:', this.blogClients); // example ๐ ~ SocketGateway ~ this.blogClients: Map(1) { '7Fu4_qBToYPCBrlkAAAB' => 2 }
// //getting all the values stored in blogClients
const allBlogClientsValues = Array.from(this.blogClients.values());
console.log(
'๐ ~ SocketGateway ~ allBlogClientsValues:',
allBlogClientsValues,
); ///example :- ๐ ~ SocketGateway ~ allBlogClientsValues: [ 2 ]
const filteredValues = allBlogClientsValues.filter((blog_id) => {
return blog_id === parsedMessage.blog_id;
});
console.log(
'๐ ~ SocketGateway ~ filteredValues ~ filteredValues:',
filteredValues,
filteredValues.length,
); //๐ ~ SocketGateway ~ filteredValues ~ filteredValues: [ 2 ] 1
this.server.emit('liveCountsSingleBlog', filteredValues.length);
}
handleDisconnect(@ConnectedSocket() client: Socket) {
this.connectedClients.delete(client.id);
this.blogClients.delete(client.id);
const countConnectedClient = Array.from(
this.connectedClients.values(),
).length;
const countConnectedBlogClient = Array.from(
this.blogClients.values(),
).length;
}
}
Remember to delete the client.id as we used client.id which implies the id generated by socket for a client when user is connected to socket. Since we are setting up that id as a key in Map when delete client.id the live visitors count also decreased.
In my WebSocketGateway implementation using NestJS, I've ensured that when a client disconnects from the WebSocket, I clean up their associated resources by removing their client.id from both the connectedClients and blogClients maps in the handleDisconnect method. This step is crucial to maintain the integrity of our system and prevent any resource leaks.
Regarding the live visitor count, I've designed it to be based on the number of clients connected to a specific blog. To achieve this, I store the blog_id associated with each client's client.id in the blogClients map. Consequently, when a client disconnects, I remove their entry from the blogClients map to accurately reflect the updated live visitor count for each blog.
While the connectedClients map exists to track client connections, I've opted to use the blogClients map directly for live visitor count tracking. This decision simplifies the logic and ensures that the live visitor count remains accurate and up to date for each blog.