NestJS et WebSockets en 2026 : Temps Réel, Gateway et Questions d'Entretien

Maîtrisez les WebSockets NestJS avec les patterns Gateway, l'intégration Socket.IO, l'authentification, la gestion des erreurs et les questions d'entretien courantes pour 2026.

Communication temps réel NestJS WebSocket avec gateway

Les WebSockets NestJS offrent une approche structurée de la communication temps réel grâce au pattern Gateway. Contrairement aux implémentations WebSocket brutes, NestJS abstrait la complexité via des décorateurs et s'intègre parfaitement au système d'injection de dépendances.

Définition Rapide

Un WebSocket Gateway dans NestJS est une classe décorée avec @WebSocketGateway() qui gère la communication bidirectionnelle entre client et serveur. Il supporte plusieurs adaptateurs (Socket.IO, ws) et s'intègre aux Guards, Pipes et Interceptors de NestJS.

Configuration d'un WebSocket Gateway avec Socket.IO

L'adaptateur par défaut dans NestJS utilise Socket.IO, qui fournit la reconnexion automatique, le support des rooms et le fallback vers le long-polling HTTP. L'installation nécessite le package spécifique à la plateforme en plus du module WebSockets principal.

bash
# terminal
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io

Un gateway basique écoute les messages entrants et peut diffuser des réponses aux clients connectés.

chat.gateway.tstypescript
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({
  cors: { origin: '*' }, // Configure CORS pour les clients navigateur
})
export class ChatGateway {
  @WebSocketServer()
  server: Server; // Accès au serveur Socket.IO sous-jacent

  @SubscribeMessage('message') // Écoute les événements 'message'
  handleMessage(
    @MessageBody() data: { text: string; room: string },
    @ConnectedSocket() client: Socket,
  ): void {
    // Diffuse à tous les clients dans la room sauf l'expéditeur
    client.to(data.room).emit('message', {
      text: data.text,
      senderId: client.id,
      timestamp: Date.now(),
    });
  }
}

Le décorateur @SubscribeMessage lie une méthode à un nom d'événement spécifique. @MessageBody() extrait le payload, tandis que @ConnectedSocket() fournit l'accès au socket client pour des réponses ciblées.

Hooks de Cycle de Vie pour la Gestion des Connexions

Les gateways NestJS implémentent des interfaces de cycle de vie pour gérer les connexions et déconnexions des clients. Ces hooks permettent le nettoyage des ressources, le suivi de présence et la validation des connexions.

presence.gateway.tstypescript
import {
  WebSocketGateway,
  OnGatewayConnection,
  OnGatewayDisconnect,
  OnGatewayInit,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger } from '@nestjs/common';

@WebSocketGateway()
export class PresenceGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
  private readonly logger = new Logger(PresenceGateway.name);
  private connectedUsers = new Map<string, { joinedAt: Date }>();

  afterInit(server: Server): void {
    // Appelé une fois lors de l'initialisation du gateway
    this.logger.log('WebSocket Gateway initialisé');
  }

  handleConnection(client: Socket): void {
    // Suivre les nouvelles connexions
    this.connectedUsers.set(client.id, { joinedAt: new Date() });
    this.logger.log(`Client connecté: ${client.id}`);
  }

  handleDisconnect(client: Socket): void {
    // Nettoyage lors de la déconnexion
    this.connectedUsers.delete(client.id);
    this.logger.log(`Client déconnecté: ${client.id}`);
  }
}

Les trois interfaces de cycle de vie servent des objectifs distincts : OnGatewayInit s'exécute une fois au démarrage, OnGatewayConnection se déclenche à chaque connexion client, et OnGatewayDisconnect gère le nettoyage quand les clients partent.

Authentification avec les Guards WebSocket

Les connexions WebSocket nécessitent une validation d'authentification avant d'autoriser l'accès aux événements protégés. Les Guards NestJS fonctionnent avec les gateways, bien que le contexte d'exécution diffère des requêtes HTTP. La documentation officielle NestJS WebSockets couvre l'intégration des guards en détail.

ws-auth.guard.tstypescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { JwtService } from '@nestjs/jwt';
import { Socket } from 'socket.io';

@Injectable()
export class WsAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  canActivate(context: ExecutionContext): boolean {
    const client: Socket = context.switchToWs().getClient();
    // Extraire le token depuis handshake auth ou query params
    const token =
      client.handshake.auth?.token ||
      client.handshake.query?.token;

    if (!token) {
      throw new WsException('Token d\'authentification manquant');
    }

    try {
      const payload = this.jwtService.verify(token as string);
      // Attacher les données utilisateur au socket pour utilisation ultérieure
      client.data.user = payload;
      return true;
    } catch {
      throw new WsException('Token invalide');
    }
  }
}

Le guard s'applique à la classe gateway ou aux gestionnaires de messages individuels via le décorateur @UseGuards(). La classe WsException lance des erreurs que le client reçoit via l'événement error de Socket.IO.

secure-chat.gateway.tstypescript
import { UseGuards } from '@nestjs/common';
import { WebSocketGateway, SubscribeMessage } from '@nestjs/websockets';
import { WsAuthGuard } from './ws-auth.guard';

@WebSocketGateway()
@UseGuards(WsAuthGuard) // Protège tous les handlers dans ce gateway
export class SecureChatGateway {
  @SubscribeMessage('privateMessage')
  handlePrivateMessage(): void {
    // Seuls les clients authentifiés atteignent ce handler
  }
}

Pour les questions d'authentification au niveau module, consultez le module d'entretien NestJS sur l'authentification et JWT couvrant les stratégies JWT et la gestion des sessions.

Prêt à réussir tes entretiens Node.js / NestJS ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Patterns de Diffusion Basés sur les Rooms

Les rooms Socket.IO permettent la livraison ciblée de messages vers des sous-ensembles de clients connectés. Les cas d'usage courants incluent les salons de chat, les lobbies de jeux et les fonctionnalités de collaboration en direct.

room.gateway.tstypescript
import {
  WebSocketGateway,
  SubscribeMessage,
  ConnectedSocket,
  MessageBody,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway()
export class RoomGateway {
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('joinRoom')
  handleJoinRoom(
    @MessageBody() roomId: string,
    @ConnectedSocket() client: Socket,
  ): { event: string; data: string } {
    // Ajouter le client à la room spécifiée
    client.join(roomId);
    // Notifier les autres dans la room
    client.to(roomId).emit('userJoined', { userId: client.id });
    return { event: 'joinedRoom', data: roomId };
  }

  @SubscribeMessage('leaveRoom')
  handleLeaveRoom(
    @MessageBody() roomId: string,
    @ConnectedSocket() client: Socket,
  ): void {
    client.leave(roomId);
    client.to(roomId).emit('userLeft', { userId: client.id });
  }

  @SubscribeMessage('roomBroadcast')
  handleRoomBroadcast(
    @MessageBody() payload: { roomId: string; message: string },
  ): void {
    // Envoyer à tous les clients dans la room, y compris l'expéditeur
    this.server.to(payload.roomId).emit('roomMessage', payload.message);
  }
}

La distinction entre client.to(room).emit() et server.to(room).emit() est importante : le premier exclut l'expéditeur, tandis que le second inclut tous les membres de la room.

Gestion des Erreurs avec les Filtres d'Exception

Les exceptions WebSocket nécessitent des filtres dédiés car les filtres d'exception HTTP ne s'appliquent pas aux contextes gateway. Les filtres personnalisés capturent WsException et formatent les réponses d'erreur pour les clients.

ws-exception.filter.tstypescript
import { Catch, ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';

@Catch(WsException)
export class WsExceptionFilter implements ExceptionFilter {
  catch(exception: WsException, host: ArgumentsHost): void {
    const client: Socket = host.switchToWs().getClient();
    const error = exception.getError();
    // Envoyer une erreur structurée au client
    client.emit('error', {
      type: 'WsException',
      message: typeof error === 'string' ? error : error,
      timestamp: new Date().toISOString(),
    });
  }
}

Les filtres s'appliquent au niveau du gateway pour une gestion cohérente des erreurs sur tous les gestionnaires de messages.

filtered.gateway.tstypescript
import { UseFilters } from '@nestjs/common';
import { WebSocketGateway } from '@nestjs/websockets';
import { WsExceptionFilter } from './ws-exception.filter';

@WebSocketGateway()
@UseFilters(WsExceptionFilter)
export class FilteredGateway {
  // Tous les handlers bénéficient d'une gestion centralisée des erreurs
}

Ce pattern reflète la philosophie NestJS des décorateurs et filtres décrite dans le module middleware et intercepteurs.

Mise à l'Échelle des WebSockets avec l'Adaptateur Redis

Les déploiements WebSocket mono-serveur échouent lorsque le load balancing distribue les clients sur plusieurs instances. L'adaptateur Redis Socket.IO synchronise les événements entre les instances via un mécanisme pub/sub.

app.module.tstypescript
import { Module } from '@nestjs/common';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
import { ChatGateway } from './chat.gateway';

@Module({
  providers: [
    ChatGateway,
    {
      provide: 'REDIS_ADAPTER',
      useFactory: async () => {
        const pubClient = createClient({ url: process.env.REDIS_URL });
        const subClient = pubClient.duplicate();
        await Promise.all([pubClient.connect(), subClient.connect()]);
        return createAdapter(pubClient, subClient);
      },
    },
  ],
})
export class AppModule {}

Le gateway utilise ensuite l'adaptateur pour la communication inter-instances.

scalable.gateway.tstypescript
import {
  WebSocketGateway,
  WebSocketServer,
  OnGatewayInit,
} from '@nestjs/websockets';
import { Inject } from '@nestjs/common';
import { Server } from 'socket.io';
import { Adapter } from 'socket.io-adapter';

@WebSocketGateway()
export class ScalableGateway implements OnGatewayInit {
  @WebSocketServer()
  server: Server;

  constructor(@Inject('REDIS_ADAPTER') private redisAdapter: Adapter) {}

  afterInit(): void {
    // Attacher l'adaptateur Redis pour le support multi-instances
    this.server.adapter(this.redisAdapter as any);
  }
}

Avec l'adaptateur Redis, un message émis sur une instance serveur atteint les clients connectés à n'importe quelle instance du cluster.

Questions d'Entretien Courantes sur les WebSockets NestJS

Les entretiens techniques sondent souvent la compréhension des décisions d'architecture temps réel et des détails d'implémentation spécifiques à NestJS.

Fréquemment Demandé

Comment NestJS gère-t-il l'authentification WebSocket différemment de HTTP ?

Les middlewares HTTP ne s'exécutent pas pour les connexions WebSocket. L'authentification se produit pendant la phase de handshake ou via des Guards appliqués au gateway. La validation du token se fait généralement dans handleConnection() ou un Guard personnalisé qui lit depuis socket.handshake.auth.

Quelle est la différence entre la configuration du port @WebSocketGateway() et le port principal de l'application ?

Par défaut, les gateways WebSocket partagent le port du serveur HTTP. Spécifier un port dans @WebSocketGateway(3001) crée un serveur WebSocket séparé sur ce port. Les ports partagés simplifient le déploiement mais nécessitent un routage basé sur le chemin (/socket.io) lors de l'utilisation de reverse proxies.

Comment tester les gateways WebSocket dans NestJS ?

Les utilitaires de test NestJS supportent le test des gateways via Test.createTestingModule(). La bibliothèque socket.io-client se connecte au serveur de test. Les hooks de cycle de vie et les gestionnaires de messages sont testés en émettant des événements et en vérifiant les réponses ou les effets de bord.

chat.gateway.spec.tstypescript
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { io, Socket } from 'socket.io-client';
import { ChatGateway } from './chat.gateway';

describe('ChatGateway', () => {
  let app: INestApplication;
  let client: Socket;

  beforeAll(async () => {
    const module = await Test.createTestingModule({
      providers: [ChatGateway],
    }).compile();

    app = module.createNestApplication();
    await app.listen(3000);
    client = io('http://localhost:3000');
  });

  afterAll(async () => {
    client.disconnect();
    await app.close();
  });

  it('reçoit un accusé de réception de message', (done) => {
    client.emit('message', { text: 'test', room: 'lobby' });
    client.on('message', (data) => {
      expect(data.text).toBe('test');
      done();
    });
  });
});

Quand utiliser l'adaptateur ws au lieu de Socket.IO ?

L'adaptateur bibliothèque ws (@nestjs/platform-ws) offre une surcharge moindre pour les applications qui n'ont pas besoin des fonctionnalités Socket.IO comme la reconnexion automatique, les rooms ou les transports de fallback. Les systèmes de trading haute fréquence et les serveurs de jeux préfèrent souvent ws pour une latence réduite.

Pour les concepts d'architecture NestJS plus larges, l'article connexe sur Guards, Intercepteurs et Architecture Modulaire couvre comment ces patterns s'intègrent à travers les contextes HTTP et WebSocket.

Conclusion

  • Les WebSocket Gateways s'intègrent à l'injection de dépendances, aux Guards et aux Filters de NestJS pour des patterns cohérents entre HTTP et endpoints temps réel
  • Les hooks de cycle de vie (OnGatewayConnection, OnGatewayDisconnect) gèrent le suivi de présence et le nettoyage des ressources
  • Les flux d'authentification diffèrent de HTTP ; la validation du token se produit pendant le handshake ou via des Guards WebSocket dédiés
  • La diffusion basée sur les rooms avec Socket.IO permet un messaging ciblé sans gérer manuellement les listes de sockets
  • L'adaptateur Redis résout la mise à l'échelle horizontale en synchronisant les événements entre plusieurs instances serveur
  • Les filtres d'exception nécessitent des implémentations spécifiques aux WebSocket utilisant WsException et des filtres personnalisés

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Partager

Articles similaires