Hey 👋, I hope you're doing well,
It's been a long time since we've seen each other!
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!
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 :
- NodeJs (It's obvious)
- Express
- socket.io
- Typescript (My forever friend)
- RxJS (Because it's great! And it will allow you to simply develop this project)
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
We init our new node project :
npm init -y
(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
Prepare the live reloading with this as script in your package.json
"scripts": {
"dev": "nodemon",
"start": "ts-node src/index.ts",
}
create a nodemon.json file bellow your package.json with :
touch nodemon.json
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"
}
Init a tsconfig file :
npx tsc --init --rootDir src --outDir dist --module commonjs --lib es2015 --sourceMap true --target es6 --esModuleInterop true
Create our folders :
mkdir src src/models src/services
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
Normally at this stage your hierarchy should look like this ⬇️
For the creation stage of the project, we are good !
Next step !
Code
Finally some code ! The 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?
});
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>
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;
}
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";
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.
});
});
}
}
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}`);
});
So if we come back to the client side, we can see all our work !
When we load the page, we wan see the numberOfUser value change, also when we left the page.
It's work !
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 !
}
}
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!
Repo Github
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
Run
npm start dev
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)
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.
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 ^^
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
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 :
SSE :
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.
Great words!!!!
Cool
Can you explain why you using express in http createServer function? I'm seeing first time this way. I'm just using app.listen.
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 :
My version :
It's explained on the expressjs source code :
github.com/expressjs/express/blob/...
and in the doc :
cf: expressjs.com/en/api.html#app.listen
In short, it's a choice of preference ^^