Fermer

mars 11, 2024

Créer une application en temps réel avec React, Node et WebSockets

Créer une application en temps réel avec React, Node et WebSockets


Apprenez à intégrer WebSockets avec React et Node.js en approfondissant les éléments fondamentaux des applications Web en temps réel. En tirant parti de Fastify et de sa bibliothèque principale WebSocket, nous verrons comment développer des applications temps réel full-stack avec des exemples de code faciles à suivre.

Dans le monde en évolution des applications Web, la fonctionnalité en temps réel est devenue une fonctionnalité essentielle, permettant des expériences utilisateur interactives et dynamiques.

Qu’il s’agisse de chats en direct, de notifications ou d’outils de collaboration, disposer d’un retour instantané est essentiel pour l’expérience utilisateur. De bons exemples sont les applications de chat où les utilisateurs peuvent voir instantanément les messages des autres ou les outils d’édition, tels que Figma ou Google Docs, qui permettent à de nombreux utilisateurs de collaborer ensemble en temps réel.

Tout cela est rendu possible grâce aux technologies temps réel, telles que les WebSockets. Dans cet article, nous tirerons parti des WebSockets et créerons une application en temps réel en utilisant React côté client et Fastify avec Node.js côté serveur.

Que sont les WebSockets ?

WebSocket est un protocole de communication puissant qui permet une communication bidirectionnelle en duplex intégral entre un client et un serveur via une connexion unique et de longue durée. Contrairement aux requêtes HTTP traditionnelles, qui sont sans état et impliquent l’ouverture d’une nouvelle connexion pour chaque requête, les WebSockets maintiennent une connexion persistante.

Avantages des WebSockets

  • Mises à jour en temps réel : Les WebSockets permettent des mises à jour et des notifications instantanées aux clients connectés.
  • Faible latence: Les WebSockets offrent une latence inférieure à celle des requêtes HTTP.
  • Bidirectionnel : Le client et le serveur peuvent s’envoyer des messages à tout moment.
  • Efficacité: Les WebSockets sont plus efficaces pour les applications nécessitant des mises à jour fréquentes ou une fonctionnalité de chat.

Inconvénients des WebSockets

  • Complexité: La mise en œuvre de WebSockets peut être plus complexe que le HTTP traditionnel.
  • Exécution du serveur : Nécessite un serveur en cours d’exécution, les WebSockets ne sont donc pas compatibles avec les environnements sans serveur, tels que les fonctions AWS Lambda ou Azure.
  • Problèmes de pare-feu : Certaines configurations de pare-feu peuvent bloquer les connexions WebSocket.

Configuration du projet

Vous pouvez trouver l’exemple de code complet de ce didacticiel dans le Dépôt GitHub.

Commençons par créer une application React côté client avec Vite et un projet côté serveur avec Fastify.

Conditions préalables

  • Nœud 20.8.1
  • Un gestionnaire de paquets, comme npm, fil ou pnpm (dans ce tutoriel, nous utiliserons npm)

Configuration d’une application React côté client avec Vite

  1. Initialisez un nouveau projet React en utilisant Vite.
npm create vite@latest client -- --template react
  1. Accédez au projet créé, installez toutes les dépendances et démarrez le serveur de développement client.
cd client
npm install
npm run dev

Une application Vite nouvellement créée s’exécute sur le port 5173, alors visitez http://localhost:5173 dans votre navigateur pour y accéder.

Début du nouveau projet Vite React

Configuration d’une application de nœud côté serveur avec Fastify

Une fois le projet Vite créé, nous devons créer le côté serveur.

  1. Créez un nouveau répertoire de serveur et initialisez un projet Fastify.
mkdir server
cd server
npm init -y
npm install fastify
  1. Créez un serveur Fastify de base.

serveur/index.mjs

import fastify from "fastify";

const app = fastify({
  logger: true,
});

app.get("https://www.telerik.com/", async (request, reply) => {
  return { hello: "world" };
});

try {
  await app.listen({ port: 3000 });
  app.log.info(`Server is running on port ${3000}`);
} catch (error) {
  app.log.error(error);
  process.exit(1);
}
  1. Modifiez le fichier package.json et ajoutez un nouveau "dev" script pour exécuter le serveur.

serveur/paquet.json

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "node --watch index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "fastify": "^4.24.3"
  }
}

Notez que le node --watch la commande n’est disponible que depuis Nœud 18. Si vous utilisez une ancienne version, vous pouvez utiliser Nodémon plutôt.

Après avoir exécuté le npm run dev commande, le serveur Fastify doit démarrer sur le port 3000. Après avoir visité http://localhost:3000vous devriez voir la réponse suivante dans le navigateur.

Réponse de base du serveur Fastify

Ajout de WebSockets pour Fastify et React

Il existe plusieurs façons d’implémenter les WebSockets côté serveur et côté client. Par exemple, nous pourrions utiliser des bibliothèques, telles que ws et socket.io. Cependant, Fastify possède une bibliothèque principale appelée @fastify/websocket qui fournit la fonctionnalité WebSocket et s’intègre bien au framework Fastify. Par conséquent, si vous utilisez Fastify dans votre projet, envisagez d’utiliser le @fastify/websocket bibliothèque. Sinon, vous pouvez utiliser d’autres solutions.

Installons @fastify/websocket et @fastify/cors dans le server annuaire.

npm install @fastify/websocket @fastify/cors

Si votre projet utilise TypeScript, assurez-vous d’installer également les types.

npm i @types/ws -D

Ensuite, nous devons enregistrer le @fastify/websocket plugin pour commencer à écouter les messages et le @fastify/cors plugin pour autoriser les connexions depuis d’autres ports. Nous devons le faire car l’application React fonctionne sur http://localhost:5137tandis que l’application Fastify fonctionnera sur http://localhost:3000.

serveur/index.mjs

import Fastify from "fastify";
import fastifyWebSockets from "@fastify/websocket";
import cors from "@fastify/cors";

const fastify = Fastify({
  logger: true,
});


fastify.register(cors);


fastify.register(fastifyWebSockets);

fastify.register(async function (fastify) {
  fastify.get(
    "/online-status",
    {
      websocket: true,
    },
    (connection, req) => {
      connection.socket.on("message", msg => {
        connection.socket.send(`Hello from Fastify. Your message is ${msg}`);
      });
    }
  );
});

fastify.get("https://www.telerik.com/", async (request, reply) => {
  return { hello: "world" };
});

try {
  await fastify.listen({ port: 3000 });
  fastify.log.info(`Server is running on port ${3000}`);
} catch (error) {
  fastify.log.error(error);
  process.exit(1);
}

Fastify transmettra toutes les connexions WebSocket au /online-status point final. Lorsqu’un nouveau message est reçu, une réponse est envoyée immédiatement.

connection.socket.send(`Hello from Fastify. Your message is ${msg}`);

Ensuite, modifions notre application React pour envoyer et recevoir des messages du serveur.

client/src/App.jsx

import { useEffect } from "react";
import "./App.css";

const ws = new WebSocket(`ws://localhost:3000/online-status`);


ws.onopen = function () {
  ws.send("hello from react");
};

function App() {
  useEffect(() => {
    
    ws.onmessage = message => {
      console.log("message from server:", message.data);
    };
  }, []);

  return <div></div>;
}

export default App;

Nous établissons une nouvelle connexion WebSocket et envoyons le message « bonjour de réaction » lorsque la connexion est ouverte.

Message du serveur après connexion

Nous avons maintenant une connexion WebSocket fonctionnelle. Modifions davantage le côté client pour afficher le nombre de tous les utilisateurs en ligne envoyés depuis le serveur. De plus, nous pouvons ajouter une sélection pour permettre aux utilisateurs de modifier leur statut en ligne.

client/src/App.jsx

import { useEffect, useState } from "react";
import "./App.css";


const userId = localStorage.getItem("userId") || Math.random();
localStorage.setItem("userId", userId);

const ws = new WebSocket(`ws://localhost:3000/online-status`);


ws.onopen = function () {
  ws.send(
    JSON.stringify({
      onlineStatus: true,
      userId,
    })
  );
};

function App() {
  
  const [usersOnlineCount, setUsersOnlineCount] = useState(0);
  
  const [onlineStatus, setOnlineStatus] = useState();

  useEffect(() => {
    
    ws.onmessage = message => {
      const data = JSON.parse(message.data);
      setUsersOnlineCount(data.onlineUsersCount);
    };
  }, []);

  const onOnlineStatusChange = e => {
    setOnlineStatus(e.target.value);
    if (!e.target.value) {
      return;
    }
    const isOnline = e.target.value === "online";
    ws.send(
      JSON.stringify({
        onlineStatus: isOnline,
        userId,
      })
    );
  };

  return (
    <div>
      <div>Users Online Count - {usersOnlineCount}</div>

      <div>My Status</div>

      <select value={onlineStatus} onChange={onOnlineStatusChange}>
        <option value="">Select Online Status</option>
        <option value="online">Online</option>
        <option value="offline">Offline</option>
      </select>
    </div>
  );
}

export default App;

Digérons le code étape par étape. Dans un premier temps, nous créons un identifiant aléatoire pour l’utilisateur et l’enregistrons dans le stockage local afin qu’il ne soit pas recréé à chaque rechargement de page.

const userId = localStorage.getItem("userId") || Math.random();
localStorage.setItem("userId", userId);

De plus, lorsqu’une connexion WebSocket est ouverte, le serveur est averti qu’un nouvel utilisateur a visité la page.


ws.onopen = function () {
  ws.send(
    JSON.stringify({
      onlineStatus: true,
      userId,
    })
  );
};

Après avoir reçu ce message, le serveur diffusera un message à tous les clients abonnés indiquant que le statut des utilisateurs en ligne a changé. Nous le mettrons en œuvre dans un instant.

Nous avons deux États. Le premier, usersOnlineCount, stockera le nombre de tous les utilisateurs en ligne. Ces informations seront envoyées depuis le serveur. Le deuxième état stocke les informations sur le statut en ligne sélectionné de l’utilisateur.


const [usersOnlineCount, setUsersOnlineCount] = useState(0);

const [onlineStatus, setOnlineStatus] = useState();

Avec useEffectnous écoutons les nouveaux messages et mettons à jour l’état en ligne des utilisateurs en conséquence.

useEffect(() => {
  
  ws.onmessage = message => {
    const data = JSON.parse(message.data);
    setUsersOnlineCount(data.onlineUsersCount);
  };
}, []);

Finalement, le onOnlineStatusChange La méthode status maintient l’état synchronisé avec le select et informe le serveur lorsque le statut de l’utilisateur est modifié.

const onOnlineStatusChange = e => {
  setOnlineStatus(e.target.value);
  if (!e.target.value) {
    return;
  }
  const isOnline = e.target.value === "online";
  ws.send(
    JSON.stringify({
      onlineStatus: isOnline,
      userId,
    })
  );
};

Mettons à jour le serveur afin qu’il stocke les utilisateurs en ligne et mette à jour le décompte chaque fois que le statut des utilisateurs en ligne est modifié.

serveur/index.mjs

import Fastify from "fastify";
import fastifyWebSockets from "@fastify/websocket";
import cors from "@fastify/cors";

const fastify = Fastify({
  logger: true,
});


fastify.register(cors);


fastify.register(fastifyWebSockets);

const usersOnline = new Set();

fastify.register(async function (fastify) {
  fastify.get(
    "/online-status",
    {
      websocket: true,
    },
    (connection, req) => {
      connection.socket.on("message", msg => {
        const data = JSON.parse(msg.toString());
        if (
          typeof data === "object" &&
          "onlineStatus" in data &&
          "userId" in data
        ) {
          
          if (data.onlineStatus && !usersOnline.has(data.userId)) {
            usersOnline.add(data.userId);
          } else if (!data.onlineStatus && usersOnline.has(data.userId)) {
            usersOnline.delete(data.userId);
          }

          
          fastify.websocketServer.clients.forEach(client => {
            if (client.readyState === 1) {
              client.send(
                JSON.stringify({
                  onlineUsersCount: usersOnline.size,
                })
              );
            }
          });
        }
      });
    }
  );
});

fastify.get("https://www.telerik.com/", async (request, reply) => {
  return { hello: "world" };
});

try {
  await fastify.listen({ port: 3000 });
  fastify.log.info(`Server is running on port ${3000}`);
} catch (error) {
  fastify.log.error(error);
  process.exit(1);
}

À la ligne 20, nous avons le usersOnline ensemble qui stocke le nombre d’utilisateurs actuellement en ligne. Dans une application réelle, ces informations pourraient être gérées à l’aide d’une solution telle que Redis, mais pour cet exemple, l’implémentation ci-dessus suffira.

Une fois qu’un utilisateur est connecté, nous écoutons les messages en utilisant connection.socket.on("message", msg => {}). Dans le on message gestionnaire, nous vérifions si le msg la valeur reçue du client est un objet avec onlineStatus et userId propriétés. Si tel est le cas, nous vérifions si le statut d’un utilisateur est en ligne ou hors ligne. En fonction du statut, nous ajoutons ou supprimons l’identifiant de l’utilisateur du usersOnline ensemble.

if (data.onlineStatus && !usersOnline.has(data.userId)) {
  usersOnline.add(data.userId);
} else if (!data.onlineStatus && usersOnline.has(data.userId)) {
  usersOnline.delete(data.userId);
}

Enfin, le changement de statut en ligne des utilisateurs est diffusé à tous les clients abonnés.

fastify.websocketServer.clients.forEach(client => {
  if (client.readyState === 1) {
    client.send(
      JSON.stringify({
        onlineUsersCount: usersOnline.size,
      })
    );
  }
});

C’est ça. Nous venons de mettre en place une application avec une fonctionnalité temps réel. Chaque fois qu’un nouvel utilisateur visite la page, tous les utilisateurs actuellement en ligne seront informés du changement de statut en ligne, comme le montre la vidéo ci-dessous.

Diffusion de l'état des WebSockets

Dans cette vidéo, la même application est visitée à l’aide de différents navigateurs pour simuler différents utilisateurs. Chaque fois qu’une nouvelle page est ouverte, le nombre d’utilisateurs est immédiatement mis à jour dans les autres navigateurs. Il change également lorsque le statut en ligne est modifié à l’aide de la fonctionnalité de sélection du statut utilisateur.

Nous pouvons utiliser un outil comme Progress Telerik Un violoniste partout pour vérifier si les WebSockets ont été correctement configurés et quels messages sont envoyés entre un client et un serveur. Fiddler Everywhere peut être utilisé comme proxy local pour intercepter et espionner les requêtes http et web socket.

Espionner les requêtes Web Socket à l'aide de Fiddler Everywhere

Le GIF ci-dessus montre comment capturer le trafic vers le http://localhost:3000/online-status point final. Au fur et à mesure que nous modifions le statut en ligne, Fiddler enregistre les messages envoyés entre les clients et le serveur. Par exemple, nous pouvons voir les messages clients envoyés lorsque l’utilisateur modifie son statut en ligne, ainsi que les messages du serveur, qui composent le nouveau nombre d’utilisateurs en ligne. Fiddler Everywhere peut afficher diverses informations sur les messages, telles que leur taille, leur contenu, la date à laquelle ils ont été envoyés, qui était l’expéditeur et plus encore.

Si vous souhaitez en savoir plus sur l’utilisation de Fiddler Everywhere pour inspecter les connexions WebSocket et bien plus encore, consultez le Documentation.

Conclusion

Dans cet article, nous avons expliqué comment créer une application en temps réel à l’aide de WebSockets, React et Fastify. Les WebSockets sont un excellent outil pour mettre en œuvre une communication en temps réel. Ce didacticiel devrait vous permettre de comprendre comment ajouter des fonctionnalités en temps réel à vos propres applications.

Gardez à l’esprit que l’exemple de ce didacticiel est très simplifié, car son objectif est de montrer comment utiliser les WebSockets. Une véritable fonctionnalité de suivi du statut en ligne devrait également permettre de détecter si un utilisateur est resté inactif pendant une période de temps spécifique, puis de modifier automatiquement son statut en mode hors ligne.




Source link