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 :
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
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 {}
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}
Chaque événement (test
, join-chat-room
) déclenche une méthode annotée @SubscribeMessage()
.
C’est ainsi que NestJS mappe tes handlers WebSocket.
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
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]);
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]);
Pense à désenregistrer tes listeners (socket.off(...)
) pour éviter les fuites mémoire.
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};
L’objet socket
importé côté route Remix doit être rendu disponible par un plugin server-only.
Server
.fetcher.Form
et Socket.io : formulaire pour persistance + WebSocket pour diffusion.utils/socket.ts
pour partager l’instance.Rappelle-toi que Socket.io gère le fallback XHR/long-polling si WebSocket n’est pas dispo.
Broadcast à la room
Modifie le handler test
pour qu’il envoie le message à tous les membres de la room, sauf l’émetteur (socket.to(room).emit(...)
).
Événement "user-typing"
Implemente un event typing
:
socket.emit('typing', conversationId)
quand l’utilisateur tape.typing
aux autres participants.Gestion de reconnexion
Simule une perte de connexion (network offline).
Observe le reconnection automatique de Socket.io et logue un message dans Chatbox
quand la connexion est rétablie.