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.

4 min read

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 :

Terminal
1
npm install socket.io # serveur NestJS
2
npm install socket.io-client # client Remix

Ajouter le module WebSocket dans NestJS

Dans ton module principal (app.module.ts), importe le module WebSocket :

src/app.module.ts
1
import { Module } from '@nestjs/common';
2
import { AppGateway } from './app.gateway';
3
4
@Module({
5
imports: [],
6
providers: [AppGateway],
7
})
8
export 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 :

src/app.gateway.ts
1
import {
2
WebSocketGateway,
3
WebSocketServer,
4
SubscribeMessage,
5
MessageBody,
6
ConnectedSocket,
7
OnGatewayInit,
8
} from '@nestjs/websockets';
9
import { Server, Socket } from 'socket.io';
10
import { SocketService } from './socket/socket.service';
11
12
@WebSocketGateway(8001, { cors: '*' })
13
export class AppGateway implements OnGatewayInit {
14
@WebSocketServer()
15
private server: Server // ^? instance Socket.io côté serveur
16
17
constructor(private socketService: SocketService) {
18
// @callout: Injection du service pour partager l'instance 'server'
19
}
20
21
afterInit() {
22
this.socketService.server = this.server;
23
}
24
25
// Envoi une confirmation dès qu'un client se connecte
26
@SubscribeMessage('connection')
27
handleConnection(@ConnectedSocket() socket: Socket) {
28
socket.emit('confirmation');
29
}
30
31
// Test de round-trip
32
@SubscribeMessage('test')
33
handleTest(
34
@MessageBody() data: any,
35
@ConnectedSocket() socket: Socket,
36
) {
37
console.log('test received:', data);
38
socket.emit('chat', "Salut, message reçu !");
39
}
40
41
// Rejoindre une room correspondant à l'ID de conversation
42
@SubscribeMessage('join-chat-room')
43
async joinRoom(
44
@MessageBody() conversationId: string,
45
@ConnectedSocket() socket: Socket,
46
) {
47
socket.join(conversationId);
48
// @callout: Le client écoute désormais tous les événements ciblant cette room
49
}
50
}

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 :

app/utils/socket.ts
1
import { io, Socket } from 'socket.io-client';
2
3
export 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 :

app/components/Chatbox.tsx
1
import { useEffect } from 'react';
2
import { socket } from '~/utils/socket';
3
4
export const Chatbox = ({ conversationId, messages, setMessages }) => {
5
useEffect(() => {
6
socket.emit('join-chat-room', conversationId);
7
// @callout: On notifie le serveur qu'on veut rejoindre la room
8
9
return () => {
10
socket.off('chat');
11
};
12
}, [conversationId]);

3. Écouter les nouveaux messages

Toujours dans Chatbox.tsx, ajoute un listener pour l’événement chat :

1
tsx app/components/Chatbox.tsx
2
useEffect(() => {
3
- // juste un state normal
4
+ // écoute des messages entrants
5
socket.on('chat', (incoming: { content: string; senderId: string }) => {
6
setMessages(old => [
7
...old,
8
{
9
id: Date.now().toString(),
10
content: incoming.content,
11
sender: { id: incoming.senderId, firstName: '??' },
12
},
13
]);
14
});
15
}, [setMessages]);

4. Envoyer un message

Dans notre exemple, on utilise un fetcher.Form de Remix, mais tu pourrais aussi émettre un événement Socket.io :

app/components/Chatbox.tsx
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 :

app/routes/conversations.$id.ts
1
import { socket } from '~/utils/socket';
2
3
export let action = async ({ request, params }) => {
4
const form = await request.formData();
5
const content = form.get('content');
6
// Persister en DB...
7
socket.to(params.id).emit('chat', { content, senderId: user.id });
8
return null;
9
};

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.Form et Socket.io : formulaire pour persistance + WebSocket pour diffusion.
  • N’oublie pas d’isoler ton code dans utils/socket.ts pour partager l’instance.

Exercices rapides

  1. 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(...)).

  2. Événement "user-typing"
    Implemente un event typing :

    • Côté client, envoie socket.emit('typing', conversationId) quand l’utilisateur tape.
    • Côté serveur, diffuse typing aux autres participants.
    • Côté client, affiche “Alice est en train d’écrire…” quand tu reçois l’event.
  3. 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.