Comprendre le protocole WebSocket de zéro et construire un système de chat complet : connexion, utilisateurs en mémoire, messages privés et groupes — sans base de données.
La bibliothèque ws est l'implémentation WebSocket de référence pour Node.js. Légère, sans dépendances, elle expose exactement l'API dont on a besoin sans rien cacher.
mkdir ws-chat && cd ws-chat
npm init -y
npm install wsStructure du projet :
ws-chat/
├── server.js
└── client.htmlconst { WebSocketServer } = require('ws')
const wss = new WebSocketServer({ port: 8080 })
wss.on('connection', (socket) => {
console.log('Nouveau client connecté')
socket.on('message', (data) => {
const message = data.toString()
console.log('Reçu :', message)
socket.send('Reçu : ' + message)
})
socket.on('close', () => {
console.log('Client déconnecté')
})
socket.on('error', (err) => {
console.error('Erreur socket :', err.message)
})
})
console.log('Serveur WebSocket sur ws://localhost:8080')Lancer le serveur :
node server.jsws expose une API événementielle. Quatre événements couvrent 95% des cas :
| Événement | Déclenché quand |
|---|---|
connection | Un client se connecte |
message | Le client envoie un message |
close | La connexion se ferme (normal ou erreur) |
error | Une erreur réseau survient |
L'objet socket reçu dans connection représente la connexion avec un client spécifique. C'est lui qu'on utilise pour envoyer des données à ce client précis ou pour stocker des informations (comme son username).
// Envoyer à ce client uniquement
socket.send('Bonjour')
// Envoyer à tous les clients connectés
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send('Message global')
}
})wss.clients est un Set de tous les sockets connectés. readyState === WebSocket.OPEN vérifie que la connexion est encore active avant d'envoyer — un client peut se déconnecter entre deux envois.
Pour des messages structurés, on sérialise en JSON :
socket.on('message', (data) => {
let parsed
try {
parsed = JSON.parse(data.toString())
} catch {
socket.send(JSON.stringify({ error: 'JSON invalide' }))
return
}
console.log('Type:', parsed.type, '| Données:', parsed)
})// Côté client ou serveur, pour envoyer :
socket.send(JSON.stringify({
type: 'chat',
content: 'Bonjour tout le monde'
}))Le champ type devient le discriminant de chaque message — c'est lui qui permettra au serveur de savoir quoi faire avec un message entrant.
Pour tester sans outil externe, un client dans le navigateur suffit :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>WS Test</title>
</head>
<body>
<script>
const ws = new WebSocket('ws://localhost:8080')
ws.onopen = () => {
console.log('Connecté')
ws.send(JSON.stringify({ type: 'ping', content: 'hello' }))
}
ws.onmessage = (event) => {
console.log('Serveur dit :', event.data)
}
ws.onclose = () => {
console.log('Déconnecté')
}
ws.onerror = (err) => {
console.error('Erreur :', err)
}
</script>
</body>
</html>Ouvrir client.html dans le navigateur (double-clic suffit pour le fichier local). La console du navigateur affiche les messages échangés.
socket.readyState peut valoir :
0 — CONNECTING : handshake en cours
1 — OPEN : connexion active
2 — CLOSING : fermeture en cours
3 — CLOSED : connexion ferméeToujours vérifier === 1 (ou === WebSocket.OPEN) avant d'envoyer — ne pas tenter d'écrire sur un socket fermé.
WebSocket a un mécanisme natif de keepalive : le serveur envoie un ping, le client répond automatiquement avec un pong. La bibliothèque ws gère les pong automatiquement, mais on peut écouter les événements :
socket.on('pong', () => {
console.log('Client toujours vivant')
})
// Envoyer un ping manuel
setInterval(() => {
if (socket.readyState === socket.OPEN) {
socket.ping()
}
}, 30000)Le serveur répond aux messages. Maintenant il faut gérer les utilisateurs — identifier chaque connexion avec un username et maintenir un registre.