DEV Community

Cover image for Socket.io | Show Number of visitor
NOPR9D ☄️
NOPR9D ☄️

Posted on • Edited on

Socket.io | Show Number of visitor

Hey 👋, I hope you're doing well,

It's been a long time since we've seen each other!

Long time


Today I want to talk to you a bit about socket.io

This very nice little library that allows us to easily manage our sockets under nodejs.

Indeed I had to use it in recent days to display a counter of current visitors in real time on my personal site.

It was very simple, so we'll see how it goes!


Let's organize all this a bit with chapters :

Table Of Contents


Creation

So, if you allow me, let's start from the beginning, the creation of the project!

Begun
We still have some expectations, in the sense that we are not going to "just" copy/paste code from the documentation.

We are going to make a small server with :


Come on, let's finally get started!

This part being quite simple, we will quickly go over the first commands.

We create a new folder for our project and enter on it :

mkdir socketio-numberOfOnlineUser-ts
cd socketio-numberOfOnlineUser-ts
Enter fullscreen mode Exit fullscreen mode

We init our new node project :

npm init -y
Enter fullscreen mode Exit fullscreen mode

(with -y cause we don't wanna loose time with the cli, -y approve all by default)


We add our beautiful dependencies ! :

npm i express rxjs socket.io
npm i -D @types/express @types/socket.io nodemon ts-node tslint typescript
Enter fullscreen mode Exit fullscreen mode

Prepare the live reloading with this as script in your package.json

  "scripts": {
    "dev": "nodemon",
    "start": "ts-node src/index.ts",
  }
Enter fullscreen mode Exit fullscreen mode

create a nodemon.json file bellow your package.json with :

touch nodemon.json
Enter fullscreen mode Exit fullscreen mode

Like this ⬇️
Nodemon config

And put this on it (it will use this config for nodemon command, your npm run dev):

{
  "ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"],
  "watch": ["src"],
  "exec": "npm start",
  "ext": "ts"
}
Enter fullscreen mode Exit fullscreen mode

Init a tsconfig file :

npx tsc --init --rootDir src --outDir dist --module commonjs --lib es2015  --sourceMap true --target es6 --esModuleInterop true
Enter fullscreen mode Exit fullscreen mode

Create our folders :

mkdir src src/models src/services
Enter fullscreen mode Exit fullscreen mode

You will get this ⬇️
Project folders

Create our files :

touch src/index.ts src/index.html src/models/index.ts src/models/Socket.ts src/services/index.ts src/services/Socket.ts
Enter fullscreen mode Exit fullscreen mode

Normally at this stage your hierarchy should look like this ⬇️
Project folders and files
For the creation stage of the project, we are good !
Next step !


Code

Finally some code ! The fun part !

Fun part
So, let's open vscode and begin with the index.ts file.

For this part, we will init a basic node server under express, you know that, no seret here so it's what's it's look like :

import express from "express"; // We import express as listener, you know.
import * as http from "http"; // We import http for init the server
// Anyway, if we don't do it, express does it secretly

const app =  express(); // Init express
const server = http.createServer(app); // Init server avec use express as listener
const port =  3000;

app.get("/", (req, res) => {
res.sendFile(__dirname +  "/index.html"); // We Will use the index.html file to see our work
});

server.listen(port, () => {
return console.log(`server is listening on ${port}`); // No need to comment that, no?
});
Enter fullscreen mode Exit fullscreen mode

At this stage we just have a nodejs server running and returning a blank page.

Let's go on.


Let's prepare our index.html file, we just want to see the number of online users in real time.

Let's put this on it :

<div>
    <p>Number of users : <span id="numberOfUsers"></span></p>
</div>
<script src="/socket.io/socket.io.js"></script>
<script>

var socket = io("localhost:3000");
var numberOfUsers = document.getElementById("numberOfUsers");

socket.on("numberOfUser", function (data) {
  numberOfUsers.textContent = data;
});
</script>
Enter fullscreen mode Exit fullscreen mode

Ah, finally our first reference to SocketIo, but what's going on?

Here we are initializing a SocketIo instance (with io('backend adress')) on the client side, this instance will try to connect to the socket "normally" available at your address localhost:3000, once connected this instance will wait for the emission of a "numberOfUser" event from your server (with socket.on('') method) .

However, your server doesn't have anything on this topic yet, so you'll just get an error if you test the page.

But let's continue.


We move on to the creation of the data model, because yes, we will send data from our socket, but to prevent the future complexity of our application we will stay clean and quickly create the data model.

Go on the src/models folder and put this into our file Socket.ts

export interface NumberOfUser {
  data?: number;
}
Enter fullscreen mode Exit fullscreen mode

Oh, but next to our Socket.ts file, we have an index.ts file, let's take care of it, in case it's a "barrel", useful to efficiently organize all our future import/export.

So, use it and put this into it :

export * from "./Socket";
Enter fullscreen mode Exit fullscreen mode

Ok, our model is ok, now we know what the data that will pass through our socket looks like.

Now let's take care of our Socket instance ! Finally !

So let's take care of the src/services folder, inside we have our little barrel index.ts (now you know what to do with it right? export blablabla)

Let's move on to the most important file, src/services/Socket.ts

We will finally use RxJS to create a variable that will be "Observable".

That is to say that a function (a callback), to be executed at each change of the data contained in this variable.

So we can make our socket react easily and dynamically!

We will also have a function that increments or decrements the numberOfUser value so that the client can see the changes when a visitor logs in/logs out.

In short here is the state of our famous file ⬇️

import * as socketio from "socket.io";
import * as http from "http"; // Just for typing, we like typing !
import { BehaviorSubject } from "rxjs";

import { NumberOfUser } from "../models/Socket"; // Here our model, cause we like typing things !

export class SocketService {
  private numberOfUser: BehaviorSubject<NumberOFUser> =
    new BehaviorSubject<NumberOFUser>({
      data: 0,
    }); // Here our "stream pipe" of the NumberOfUser Data.
  private numberOfUser$: Observable<NumberOFUser> =
    this.numberOfUser.asObservable(); // Here's our "Observable" data.

  private io!: socketio.Server; // Our Socket instance

  constructor(server: http.Server) {
    this.io = new socketio.Server(server, { cors: { origin: "*" } }); // We init our Socket instance with the server as parameter
  }

  public listenStore() {
    this.numberOfUser$.subscribe((store) => {
      // subscribe is called at each store value change !
      this.io.emit("numberOfUser", store.data); // So at each value change, we emit this value to the client with the event name "numberOfUser"
    });
  }

  public listenUserActivity() {
    this.io.on("connection", (client) => {
      // When a user do a connection at our socket, the "connection" event is called. It's a default event from socket.io.
      this.numberOfUser.next({
        data: this.numberOfUser.value.data + 1, // we get the actual value of our "Observable" and
      }); // we change the data with the function "next"

      client.once("disconnect", () => {
        // When a user do a disconnection, the "disconnect" event is called. It's a default event from socket.io.
        this.numberOfUser.next({
          data: this.numberOfUser.value.data - 1,
        }); // You know what next do now.
      });
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

And that it for the socket.ts file !


Now that we have a ready-made Socket service with the logic we want, we need to let our server know it exists.

To do this we will edit the src/index.ts file like this

import express from "express";
import * as http from "http";
import { SocketService } from "./services"; // We import our SocketService, Thanks to the index.ts file, we don't need to specify where exactly the Socket.ts file is located.

const app = express();
const server = http.createServer(app);
const port = 3000;

const socketService = new SocketService(server); // We instantiate our SocketService

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

socketService.listenStore(); // We ask to our service to listen the change of the "Observable" data and emit the change to our socket by the "numberOfUser" event.

socketService.listenUserActivity(); // We listen the activity of the socket and change the state of the "Observable" data when a desired event is triggered ("connection" or "disconnect")

server.listen(port, () => {
  return console.log(`server is listening on ${port}`);
});

Enter fullscreen mode Exit fullscreen mode

So if we come back to the client side, we can see all our work !

Socket result

When we load the page, we wan see the numberOfUser value change, also when we left the page.

It's work !

Mission accomplished

So you can start from there to make your logic more complex !

Extra

But what are you saying? Your client is not on the node side? You are using a front framework like Vuejs/Angular or Next.

No problem, just use the socket.io-client package on the front side and do like this ⬇️:

import { io, Socket } from "socket.io-client";
import { Observable, BehaviorSubject } from "rxjs";

export interface SocketData {
  numberOfUser?: number;
}

export class SocketService {
  private socket!: Socket;

  private store: BehaviorSubject<SocketData> = new BehaviorSubject<SocketData>({
    numberOfUser: 0,
  });
  private store$: Observable<SocketData> = this.store.asObservable();

  constructor() {
    this.socket = io("YOUR_BACKEND_URL");
    this.socket.on("numberOfUser", (data) => {
      this.emit(data); // At each "numberOfUser" event we modify our "Observable" data.
    });
  }

  public emit(store: SocketData): void {
    this.store.next(store);
  }

  public listen(): Observable<SocketData> {
    return this.store$; // You will be able to use this.socketService.listen().subscribe(), it's the same logic as we see above !
  }
}
Enter fullscreen mode Exit fullscreen mode

I think we've seen the main part, so we can stop here!

In the next post, we will see how to deploy this under your own VPS under apache2, you will see it's simple!

Ok, Goodbye !

See you !

Repo Github

GitHub logo NOPR9D / socketio-numberOfOnlineUser-ts

Code of the devto article "SocketIo Show Number of visitor"

Socket.io Show number of visitor

Code of the article Socket.io | Show Number of visitor

How to

Install

npm i
Enter fullscreen mode Exit fullscreen mode

Run

npm start dev
Enter fullscreen mode Exit fullscreen mode

Open an explorer on the adress : localhost:3000






My portfolio in development where I implemented this socket

If you have any ideas, I'm listening! I hesitate to allow users to send each other emoji for example

edit 11/14/2022 : Set things a little more generic / Or simpler to add other "Observables" value

Top comments (8)

Collapse
 
raibtoffoletto profile image
Raí B. Toffoletto

If what you need is a simple counter, yes, but if you need to listen to more namespaced events (routes) from the server, browsers limit SSE to 6 connections per page... with socket.io you have two-way data communication and unlimited possibilities. Been working with both and I far prefer socket.io.

 
noprod profile image
NOPR9D ☄️

I admit that everything you said made me curious

I will try the SSE on another project, but for this portfolio, I keep Socketio for the moment ^^

To conclude, in my eyes I am not totally right but I am not totally wrong in the approach.

it depends on the point of view...

But yes, it's overkill if we limit ourselves to just counting, you're not wrong about that.

You will surely see an article on the SSE once I have tried that ^^

Collapse
 
noprod profile image
NOPR9D ☄️

You are not wrong ^^

But I must not have been explicit enough on this and I apologize, it is mainly to be able to use this base in order to start on something more complex

The example is only for user activity counting, but I plan to use it for other events (online games, etc.)

Also @raibtoffoletto listed all the good points of socket.io

In any case, I admit that I should have been clearer on the fact that it is a base to then go on something more complex, I apologize

I'm sorry

 
noprod profile image
NOPR9D ☄️

You know, even if it means comparing as much, doing it with discernment.

As for this topic, personally I will then need to easily manage more than the simple account, and as specified it is a basis for then starting on something more complete.

So this project is not intended to remain a simple counter but much more ;)

Regarding SSE vs WS

In my eyes the choice is simple, I want something fast, simple to manage and above all to be sure that it works well for everyone.

WS :
Image description

SSE :
Image description

The websockets cover more browsers than the SSE.


Regarding the use of google, I don't know what they are using right now, I guess a custom/house version of the SSE, but previously they were under XMPP from what I could see in my research.

However, you cannot compare the tools of a large group that can afford to support 100/200 http calls per page load and 50/100 calls as soon as you touch something to monitor you.

I don't want to have any problems with my request limit given my VPS, don't want to play with PHP-FPM and its config, so the Websocket = winner too. (Also i have several subdomains and I much prefer to minimize the number of calls)

You're probably right in the context of a large company, but in my eyes not in the context of a home server managed by one person.

And I repeat myself, but it is a "base" to complicate later, it should not be limited to the counter.

Otherwise 3/4 of what is explained is useless. And as much to do everything in a simple js file without getting bored for the rest.

Collapse
 
mhcrocky profile image
mhcrocky

Great words!!!!

Collapse
 
ds_sombol profile image
chabsombol

Cool

Collapse
 
byraptiye profile image
Mucahit Yilmaz

Can you explain why you using express in http createServer function? I'm seeing first time this way. I'm just using app.listen.

Collapse
 
noprod profile image
NOPR9D ☄️ • Edited

You can do both ways, as you wish.

However I always prefer to pass the instance of express in a createServer function because it allows to have more control over the code and to better divide the code

You can easily use 'http' and 'https' together if needed for example and the 'server.listen' will always be at the end of the file

in short,

Your version with just listen :

var express   = require('express');
var app       = express();
var server    = app.listen(3000);
var io        = require('socket.io').listen(server);

io.sockets.on('connection', function (socket) {
  ...[](url)
});
Enter fullscreen mode Exit fullscreen mode

My version :

var express = require('express');
var app     = express();
var server  = require('http').createServer(app);
var io      = require('socket.io').listen(server);

server.listen(3000);
Enter fullscreen mode Exit fullscreen mode

It's explained on the expressjs source code :

Image description
github.com/expressjs/express/blob/...

and in the doc :

Image description

cf: expressjs.com/en/api.html#app.listen

In short, it's a choice of preference ^^