iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
Node.js / HTML · Débutant

WebSocket

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.

Node.jswsJavaScriptHTML
01Le protocole WebSocket02Premier serveur WebSocket03Gérer les utilisateurs en mémoire04Chat global et broadcast05Messages privés06Groupes et rooms07Interface client complète
Chapitre 6·25 min

Groupes et rooms

Un groupe (ou room) est un canal partagé par un sous-ensemble d'utilisateurs. Seuls les membres reçoivent les messages envoyés dans ce groupe. Plusieurs groupes peuvent coexister simultanément, et un utilisateur peut appartenir à plusieurs groupes en même temps.

Le registre des groupes

Un second Map stocke l'état des groupes :

server.js
// nom du groupe → Set de usernames membres
const groups = new Map()

On utilise un Set de usernames (pas de sockets) pour les membres — les sockets changent à chaque reconnexion, les usernames non.

Créer ou rejoindre un groupe

server.js
case 'join':
  handleJoin(socket, msg)
  break
server.js
function handleJoin(socket, msg) {
  const groupName = (msg.group || '').trim()
 
  if (!groupName) {
    socket.send(JSON.stringify({ type: 'error', content: 'Nom de groupe manquant' }))
    return
  }
 
  // Créer le groupe s'il n'existe pas
  if (!groups.has(groupName)) {
    groups.set(groupName, new Set())
  }
 
  const members = groups.get(groupName)
 
  if (members.has(socket.username)) {
    socket.send(JSON.stringify({ type: 'error', content: `Déjà membre de ${groupName}` }))
    return
  }
 
  members.add(socket.username)
 
  // Confirmer à l'utilisateur
  socket.send(JSON.stringify({
    type: 'joined',
    group: groupName,
    members: [...members],
  }))
 
  // Notifier les autres membres du groupe
  broadcastGroup(groupName, {
    type: 'system',
    group: groupName,
    content: `${socket.username} a rejoint ${groupName}`,
  }, socket.username)
 
  console.log(`${socket.username} a rejoint le groupe "${groupName}" (${members.size} membres)`)
}

Quitter un groupe

server.js
case 'leave':
  handleLeave(socket, msg)
  break
server.js
function handleLeave(socket, msg) {
  const groupName = (msg.group || '').trim()
  const members = groups.get(groupName)
 
  if (!members || !members.has(socket.username)) {
    socket.send(JSON.stringify({ type: 'error', content: `Vous n'êtes pas dans ${groupName}` }))
    return
  }
 
  members.delete(socket.username)
 
  if (members.size === 0) {
    groups.delete(groupName) // supprimer le groupe vide
  } else {
    broadcastGroup(groupName, {
      type: 'system',
      group: groupName,
      content: `${socket.username} a quitté ${groupName}`,
    })
  }
 
  socket.send(JSON.stringify({ type: 'left', group: groupName }))
}

Envoyer un message dans un groupe

server.js
case 'group':
  handleGroupMessage(socket, msg)
  break
server.js
function handleGroupMessage(socket, msg) {
  const groupName = (msg.group || '').trim()
  const content = (msg.content || '').trim()
 
  if (!groupName || !content) return
 
  const members = groups.get(groupName)
 
  if (!members || !members.has(socket.username)) {
    socket.send(JSON.stringify({
      type: 'error',
      content: `Rejoignez d'abord le groupe "${groupName}"`,
    }))
    return
  }
 
  broadcastGroup(groupName, {
    type: 'group',
    group: groupName,
    from: socket.username,
    content,
    at: new Date().toISOString(),
  })
}

Broadcast vers un groupe

server.js
function broadcastGroup(groupName, msg, excludeUsername = null) {
  const members = groups.get(groupName)
  if (!members) return
 
  const data = JSON.stringify(msg)
 
  members.forEach((username) => {
    if (username === excludeUsername) return
    const client = users.get(username)
    if (client && client.readyState === WebSocket.OPEN) {
      client.send(data)
    }
  })
}

La double lookup (members → usernames, users → socket) garantit qu'on envoie uniquement aux membres actuellement connectés. Un membre hors-ligne ne reçoit pas le message — et c'est attendu, puisqu'on n'a pas de base de données pour le stocker.

Lister les groupes disponibles

server.js
case 'groups':
  socket.send(JSON.stringify({
    type: 'groups',
    list: [...groups.entries()].map(([name, members]) => ({
      name,
      count: members.size,
      members: [...members],
    })),
  }))
  break

Test complet avec trois utilisateurs

Alice, Bob et Carol connectés et inscrits.

Alice crée le groupe dev :

ws.send(JSON.stringify({ type: 'join', group: 'dev' }))

Bob rejoint :

ws.send(JSON.stringify({ type: 'join', group: 'dev' }))

Alice reçoit : "bob a rejoint dev". Carol ne reçoit rien — elle n'est pas dans le groupe.

Alice envoie un message dans le groupe :

ws.send(JSON.stringify({ type: 'group', group: 'dev', content: 'Quelqu\'un connaît ws ?' }))

Alice et Bob reçoivent :

[dev] alice : Quelqu'un connaît ws ?

Carol ne reçoit rien.

Nettoyer à la déconnexion

Quand un utilisateur se déconnecte, le retirer de tous ses groupes :

server.js
socket.on('close', () => {
  if (!socket.username) return
 
  // Retirer de tous les groupes
  groups.forEach((members, groupName) => {
    if (members.has(socket.username)) {
      members.delete(socket.username)
      if (members.size === 0) {
        groups.delete(groupName)
      } else {
        broadcastGroup(groupName, {
          type: 'system',
          group: groupName,
          content: `${socket.username} s'est déconnecté`,
        })
      }
    }
  })
 
  users.delete(socket.username)
  broadcast({ type: 'system', content: `${socket.username} a quitté le chat` })
})

Le système de groupes est complet. Le dernier chapitre assemble tout dans une interface HTML fonctionnelle.

Précédent
Messages privés
Suivant
Interface client complète

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main