Envoyer et recevoir des messages en temps réel (Socket.io)
Connexion à la room, envoi de message, réception instantanée via WebSocket, feedback visuel.
Dans cette leçon, tu vas découvrir comment envoyer et recevoir des messages en temps réel grâce à Socket.io, en s’appuyant sur un backend NestJS et un frontend Remix.
Tu verras comment :
- Configurer un Gateway WebSocket dans NestJS
- Initialiser un client Socket.io dans Remix
- Écouter et émettre des événements pour synchroniser tes messages instantanément
Installer et configurer Socket.io
Commence par installer les paquets nécessaires à la fois côté serveur et client :
1npm install socket.io # serveur NestJS2npm install socket.io-client # client Remix
Ajouter le module WebSocket dans NestJS
Dans ton module principal (app.module.ts), importe le module WebSocket :
1import { Module } from '@nestjs/common';2import { AppGateway } from './app.gateway';34@Module({5imports: [],6providers: [AppGateway],7})8export class AppModule {}
Côté serveur : Gateway NestJS
Le cœur de la communication temps réel se situe dans un Gateway. Voici un exemple complet :
1import {2WebSocketGateway,3WebSocketServer,4SubscribeMessage,5MessageBody,6ConnectedSocket,7OnGatewayInit,8} from '@nestjs/websockets';9import { Server, Socket } from 'socket.io';10import { SocketService } from './socket/socket.service';1112@WebSocketGateway(8001, { cors: '*' })13export class AppGateway implements OnGatewayInit {14@WebSocketServer()15private server: Server // ^? instance Socket.io côté serveur1617constructor(private socketService: SocketService) {18// @callout: Injection du service pour partager l'instance 'server'19}2021afterInit() {22this.socketService.server = this.server;23}2425// Envoi une confirmation dès qu'un client se connecte26@SubscribeMessage('connection')27handleConnection(@ConnectedSocket() socket: Socket) {28socket.emit('confirmation');29}3031// Test de round-trip32@SubscribeMessage('test')33handleTest(34@MessageBody() data: any,35@ConnectedSocket() socket: Socket,36) {37console.log('test received:', data);38socket.emit('chat', "Salut, message reçu !");39}4041// Rejoindre une room correspondant à l'ID de conversation42@SubscribeMessage('join-chat-room')43async joinRoom(44@MessageBody() conversationId: string,45@ConnectedSocket() socket: Socket,46) {47socket.join(conversationId);48// @callout: Le client écoute désormais tous les événements ciblant cette room49}50}
Tip
Chaque événement (test, join-chat-room) déclenche une méthode annotée @SubscribeMessage().
C’est ainsi que NestJS mappe tes handlers WebSocket.
Côté client : intégration dans Remix
1. Initialiser le client Socket.io
Crée un utilitaire pour partager l’instance socket dans tout ton projet :
1import { io, Socket } from 'socket.io-client';23export const socket: Socket = io('http://localhost:8001'); // ^? connexion automatique
2. Rejoindre une room côté client
Dans ton composant de chat (Chatbox.tsx), émet l’événement join-chat-room dès le montage :
1import { useEffect } from 'react';2import { socket } from '~/utils/socket';34export const Chatbox = ({ conversationId, messages, setMessages }) => {5useEffect(() => {6socket.emit('join-chat-room', conversationId);7// @callout: On notifie le serveur qu'on veut rejoindre la room89return () => {10socket.off('chat');11};12}, [conversationId]);
3. Écouter les nouveaux messages
Toujours dans Chatbox.tsx, ajoute un listener pour l’événement chat :
1tsx app/components/Chatbox.tsx2useEffect(() => {3- // juste un state normal4+ // écoute des messages entrants5socket.on('chat', (incoming: { content: string; senderId: string }) => {6setMessages(old => [7...old,8{9id: Date.now().toString(),10content: incoming.content,11sender: { id: incoming.senderId, firstName: '??' },12},13]);14});15}, [setMessages]);
Pro tip
Pense à désenregistrer tes listeners (socket.off(...)) pour éviter les fuites mémoire.
4. Envoyer un message
Dans notre exemple, on utilise un fetcher.Form de Remix, mais tu pourrais aussi émettre un événement Socket.io :
1<fetcher.Form method="POST" action={`/conversations/${conversationId}`}>2<input name="content" placeholder="Message..." />3<button type="submit">Envoyer</button>4</fetcher.Form>
Ensuite, dans ton loader/action Remix, tu persistes en base (via Prisma) et tu rediffuses :
1import { socket } from '~/utils/socket';23export let action = async ({ request, params }) => {4const form = await request.formData();5const content = form.get('content');6// Persister en DB...7socket.to(params.id).emit('chat', { content, senderId: user.id });8return null;9};
Server-only
L’objet socket importé côté route Remix doit être rendu disponible par un plugin server-only.
Points clés
- NestJS @WebSocketGateway : mappe les événements et gère l’instance
Server. - Rooms Socket.io : partitionne tes clients par conversation.
- Côté client, join + on/off garantissent une synchro précise.
- Tu peux mixer
fetcher.Formet Socket.io : formulaire pour persistance + WebSocket pour diffusion. - N’oublie pas d’isoler ton code dans
utils/socket.tspour partager l’instance.
Tip
Rappelle-toi que Socket.io gère le fallback XHR/long-polling si WebSocket n’est pas dispo.
Exercices rapides
-
Broadcast à la room
Modifie le handlertestpour qu’il envoie le message à tous les membres de la room, sauf l’émetteur (socket.to(room).emit(...)). -
Événement "user-typing"
Implemente un eventtyping:- Côté client, envoie
socket.emit('typing', conversationId)quand l’utilisateur tape. - Côté serveur, diffuse
typingaux autres participants. - Côté client, affiche “Alice est en train d’écrire…” quand tu reçois l’event.
- Côté client, envoie
-
Gestion de reconnexion
Simule une perte de connexion (network offline).
Observe le reconnection automatique de Socket.io et logue un message dansChatboxquand la connexion est rétablie.