Aller au contenu principal
~/GiwiSoft
Ws01

WebSockets avec Socket.IO : chat, notifications, temps réel

Giwi 5 min de lecture Web, JavaScript

Le HTTP classique suit un modèle requête-réponse : le client demande, le serveur répond. Mais pour du temps réel - notifications, chat, mise à jour de tableaux de bord - ce modèle montre ses limites. Le client devrait interroger le serveur en permanence pour savoir s’il y a du nouveau.

Les WebSocket établissent une connexion bidirectionnelle persistante. Et Socket.IO simplifie tout ça.

WebSocket vs HTTP

HTTP

sequenceDiagram participant Client participant Serveur Client->>Serveur: requête (HTTP) Serveur-->>Client: réponse (HTTP) Note over Client,Serveur: connexion fermée

WebSocket

sequenceDiagram participant Client participant Serveur Client->>Serveur: connexion WebSocket Note over Client,Serveur: connexion persistante, bidirectionnelle Serveur->>Client: message Client->>Serveur: message Client->>Serveur: message Serveur->>Client: message Note over Client,Serveur: Les deux peuvent envoyer
des messages à tout moment

Pourquoi Socket.IO plutôt que les WebSocket natives ?

Socket.IO ajoute plusieurs fonctionnalités cruciales :

  • Reconnexion automatique : si la connexion tombe, elle se rétablit
  • Fallback : si WebSocket n’est pas supporté, il utilise du long-polling HTTP
  • Rooms : groupes de sockets pour broadcaster
  • Namespaces : séparation logique des canaux
  • Événements : on travaille avec des événements nommés, pas des messages bruts

Mise en place du serveur

npm install socket.io
import { Server as HttpServer } from "http";
import { Server } from "socket.io";
import express from "express";

const app = express();
const httpServer = new HttpServer(app);
const io = new Server(httpServer, {
cors: {
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
},
pingTimeout: 60000,
pingInterval: 25000,
});

httpServer.listen(4000, () => {
console.log("Socket.IO server ready on port 4000");
});

pingTimeout/pingInterval : ces paramètres permettent de détecter les connexions mortes. Le serveur envoie un ping toutes les 25s, et si pas de réponse en 60s, il ferme la connexion.

Gestion des connexions

io.on("connection", (socket) => {
console.log(`Client connecté : ${socket.id}`);

socket.on("disconnect", (reason) => {
console.log(`Client déconnecté ${socket.id} : ${reason}`);
});

socket.on("error", (err) => {
console.error(`Erreur sur ${socket.id} :`, err.message);
});
});

Chat en temps réel

Le cas d’usage le plus classique. Côté serveur :

io.on("connection", (socket) => {
// Rejoindre un salon de discussion
socket.on("join:room", (room: string) => {
socket.join(room);
socket.to(room).emit("notification", {
type: "join",
username: socket.data.username,
});
});

// Quitter un salon
socket.on("leave:room", (room: string) => {
socket.leave(room);
});

// Message dans un salon
socket.on("message:send", ({ room, content }) => {
const message = {
id: crypto.randomUUID(),
username: socket.data.username,
content,
timestamp: new Date().toISOString(),
};

// Broadcast à tout le salon (sauf l'émetteur)
socket.to(room).emit("message:new", message);
// Confirmation à l'émetteur
socket.emit("message:sent", message);
});
});

Côté client (navigateur) :

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

const socket: Socket = io("http://localhost:4000", {
auth: { token: "jwt-token" },
});

socket.on("connect", () => {
console.log("Connecté au serveur");
});

// Écouter les nouveaux messages
socket.on("message:new", (message) => {
displayMessage(message);
});

// Envoyer un message
document.getElementById("send-btn")?.addEventListener("click", () => {
const input = document.getElementById("msg-input") as HTMLInputElement;
socket.emit("message:send", {
room: "general",
content: input.value,
});
input.value = "";
});

Notifications en temps réel

Un cas que j’utilise pour alerter sur l’état de mon infrastructure :

// Service de monitoring qui notifie via Socket.IO
class NotificationService {
constructor(private io: Server) {}

async sendAlert(alert: Alert) {
// Envoyer aux admins connectés
this.io.to("admins").emit("alert:critical", {
id: alert.id,
severity: alert.severity,
message: alert.message,
timestamp: new Date(),
});

// Notification moins urgente pour tout le monde
if (alert.severity === "info") {
this.io.emit("notification", {
type: "info",
message: alert.message,
});
}
}

async sendMetricUpdate(metric: Metric) {
// Envoyer uniquement aux clients qui regardent ce dashboard
this.io.to(`dashboard:${metric.dashboardId}`).emit("metric:update", metric);
}
}

// Dans le monitoring
monitor.on("cpu:high", (data) => {
notifier.sendAlert({
severity: "warning",
message: `CPU à ${data.percent}% sur ${data.host}`,
});
});

Tableau de bord temps réel

Pour un dashboard qui se met à jour automatiquement :

// Serveur : push périodique des métriques
setInterval(() => {
const stats = {
users: await getActiveUsers(),
requests: await getRequestCount(),
cpu: await getCpuUsage(),
memory: await getMemoryUsage(),
};

io.to("dashboard:main").emit("stats:update", stats);
}, 5000);
// Client : abonnement et mise à jour de l'UI
const socket = io("http://localhost:4000");

socket.emit("subscribe:dashboard", "main");

socket.on("stats:update", (stats) => {
document.getElementById("cpu-bar")!.style.width = `${stats.cpu}%`;
document.getElementById("mem-value")!.textContent = `${stats.memory}%`;
});

Namespaces : séparer les préoccupations

Les namespaces permettent de cloisonner les communications :

// Namespace public : chat et notifications
const chatNamespace = io.of("/chat");
chatNamespace.on("connection", (socket) => {
// logique du chat uniquement
});

// Namespace privé : monitoring
const adminNamespace = io.of("/admin");
adminNamespace.use((socket, next) => {
// Middleware d'authentification
const token = socket.handshake.auth.token;
if (!verifyToken(token)) {
return next(new Error("Non autorisé"));
}
next();
});

adminNamespace.on("connection", (socket) => {
socket.join("admins");
// logique d'administration
});

Middlewares d’authentification

Sécuriser les connexions Socket.IO :

io.use((socket, next) => {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error("Token manquant"));
}

try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
socket.data.userId = payload.sub;
socket.data.username = payload.username;
socket.data.roles = payload.roles;
next();
} catch {
next(new Error("Token invalide"));
}
});

Gestion des rooms et broadcast

// Envoyer à un utilisateur spécifique
io.to(socketId).emit("private:message", data);

// Envoyer à tous sauf l'émetteur
socket.broadcast.emit("user:online", username);

// Envoyer à tous
io.emit("system:maintenance", { at: "2026-07-01T02:00:00Z" });

// Compter les connexions dans une room
const count = io.sockets.adapter.rooms.get("general")?.size || 0;

Monitoring et statistiques

Socket.IO expose des métriques utiles :

setInterval(() => {
const stats = {
connected: io.engine.clientsCount,
rooms: io.sockets.adapter.rooms.size,
namespaces: io._nsps.size,
};
console.table(stats);
}, 30000);

Conclusion

Socket.IO est la solution la plus mature pour ajouter du temps réel à vos applications Node.js. Avec sa gestion automatique de la reconnexion, ses rooms, ses namespaces, et son fallback HTTP, il résout tous les problèmes des WebSocket natives.

J’utilise Socket.IO pour le chat de mon outil de collaboration, les notifications d’infrastructure, et les mises à jour de dashboards. C’est un outil fiable qui tourne depuis des mois sans intervention.

Et vous, quel cas d’usage temps réel comptez-vous implémenter ?