Visualiser des données en temps réel dans Node.js avec Twilio Sync

February 21, 2020
Rédigé par
Chuks Opia
Contributeur
Les opinions exprimées par les contributeurs de Twilio sont les leurs

Implémenter une visualisation des données en temps réel dans Node.js avec Twilio Sync

Dans le monde d'aujourd'hui, presque toutes les applications existantes sont des applications en temps réel. Des applications de chat aux outils de collaboration, en passant par les jeux en ligne et les applications de covoiturage, les utilisateurs attendent d'une application qu'elle offre des mises à jour instantanées à mesure que d'autres utilisateurs interagissent avec elle. Cela montre bien à quel point synchroniser l'état d'une application est important pour la construction d'applications modernes et interactives.

Twilio Sync offre une API de synchronisation d'état permettant de gérer à grande échelle la synchronisation de l'état d'applications entre plusieurs périphériques et utilisateurs.

Dans ce post, vous apprendrez à ajouter une fonctionnalité de temps réel à une application Node.js à l'aide de Sync. Vous construirez une application d'enquête sportive affichant en temps réel les résultats de l'enquête.

Conditions préalables

Pour construire le projet qui sert d'étude de cas à ce post, les outils de développement suivants doivent être installés sur votre système :

  • Environnement d'exécution JavaScript Node.js
    (Le programme d'installation de Node.js inclut le gestionnaire de packages npm, qui est également requis.)
  • Un compte Twilio : créez un compte d'essai gratuit à partir de ce lien et vous recevrez un crédit de 10 $.
  • Git : le contrôle du code source est votre ami.

Ce tutoriel nécessite une connaissance de base de JavaScript. Une expérience préalable avec Node.js et Express sera utile.

Le référentiel associé à ce post est disponible sur GitHub.

Configuration de l'environnement de développement

Commencez par créer sur votre ordinateur un répertoire de projet twilio_voting.

Dans ce répertoire twilio_voting, créez un nouveau projet Node.js à l'aide de l'outil de scafolding du framework d'application Web Express. Exécutez les instructions de ligne de commande suivantes dans le répertoire twilio_voting :

git init
npx gitignore node
npx license mit > LICENSE
npx express-generator --view=ejs -f
npm install
git add -A
git commit -m "Initial commit"

La première commande ci-dessus initialise un référentiel Git vide. La deuxième commande crée un fichier .gitignore qui exclut les fichiers qui ne devraient pas être suivis par Git, y compris le répertoire node_modules du projet. La troisième ligne ajoute une licence de logiciel open source MIT, tandis que la quatrième ligne installe express-generator.

L'indicateur -f de la quatrième commande force l'installation d'express-generator dans un répertoire non vide. La commande suivante installe toutes les dépendances ajoutées par la bibliothèque express-generator. Les deux dernières commandes ajoutent tous les fichiers initialisés au référentiel Git.

Pour terminer la configuration du projet, exécutez les commandes ci-dessous dans votre fenêtre de terminal afin d'installer les autres dépendances requises :

npm install twilio dotenv

Une fois toutes les dépendances installées, exécutez npm start dans la fenêtre de votre console et rendez-vous sur http://localhost:3000/ dans votre navigateur pour voir le message de bienvenue d'Express.

Obtention des informations d'identification Twilio

Twilio authentifie les requêtes API à l'aide de certaines informations d'identification. Celles-ci incluent le Account SID et le Auth Token (token d'authentification). Outre ces deux informations d'identification, vous avez également besoin de clés API révocables pour signer les tokens d'accès qui sont utilisés par les SDK de communication en temps réel de Twilio.

Pour accéder à votre Account SID et à votre Auth Token, connectez-vous au tableau de bord de la console de votre projet Twilio et copiez-les dans un emplacement sécurisé. Vous trouverez ces valeurs dans le coin supérieur droit du tableau de bord, sous le nom du projet.

Pour générer vos clés API, sélectionnez Tableau de bord > Paramètres > Clés API dans le panneau de navigation gauche. Cliquez sur + (le signe plus) pour créer une nouvelle clé API. Dans le panneau New API Key (Nouvelle clé API), saisissez une valeur descriptive, telle que « Twilio Voting » dans le champ Friendly Name (Nom convivial). Laissez le champ Key Type (Type de clé) défini sur « Standard ». Cliquez sur le bouton Create API Key (Créer une clé API) pour générer vos clés API. Veillez à copier votre SID et vos clés API secrètes dans un emplacement sûr avant de fermer la page, car elles ne seront affichées qu'une seule fois.

Stockage des variables d'environnement

Les clés et tokens secrets devraient être stockés et accessibles en privé, et à ce titre le seront en tant que variables d'environnement.

Dans le répertoire racine du projet, créez un fichier .env et ajoutez ce qui suit au fichier, en remplaçant les valeurs spécifiques par les informations correspondantes issues de vos comptes Twilio :

TWILIO_ACCOUNT_SID=IM2c20qd0qq2436eqe1ebb5a3b9e23aeq9
TWILIO_AUTH_TOKEN=f38efqa1e3a45b450e1xutf23v1411a5
TWILIO_API_KEY=F38efqa1e3a45b450e122a1e3a45b450e1223535efe1ebb
TWILIO_API_SECRET=F38efqa1e3a22a1elIigZJCVPaiia14W45b450e

Si vous avez suivi les instructions concernant la configuration du projet, .env devrait déjà avoir été ajouté à votre fichier .gitignore. Dans le cas contraire, ajoutez .env à votre fichier .gitignore afin de vous assurer que vos informations d'identification sont sécurisées.

Construction de la page d'accueil

La page d'accueil de l'application d'enquête sportive contiendra un formulaire avec trois boutons radio, une section de graphique permettant de visualiser les résultats de l'enquête et une section de statistiques montrant un résumé des statistiques de l'enquête.

Remplacez le contenu existant du fichier views/index.ejs par le HTML suivant :

<!DOCTYPE html>
<html>

<head>
  <title><%= title %></title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <link rel="stylesheet" href="/stylesheets/style.css" />
  <link href="https://fonts.googleapis.com/css?family=Rubik:400,500&display=swap" rel="stylesheet" />
</head>

<body>
  <div class="container">
    <main class="main">
      <div class="survey">
        <div class="nominees">
          <div class="heading">
            <h1 class="header">
              What is the most popular sport in the world?
            </h1>
          </div>
          <div class="form">
            <form>
              <div class="form-group">
                <label for="basketball" class="wrapper">
                  Basketball
                  <input type="radio" name="vote" id="basketball" required />
                  <span class="checkmark"></span>
                </label>
              </div>
              <div class="form-group">
                <label for="cricket" class="wrapper">
                  Cricket
                  <input type="radio" name="vote" id="cricket" required />
                  <span class="checkmark"></span>
                </label>
              </div>
              <div class="form-group">
                <label for="football" class="wrapper">
                  Football
                  <input type="radio" name="vote" id="football" required />
                  <span class="checkmark"></span>
                </label>
              </div>

              <div class="form-group">
                <button type="submit">Submit</button>
              </div>
            </form>
          </div>
        </div>
        <div class="chart">
          <canvas id="voteChart" width="400" height="400"></canvas>
        </div>
      </div>
      <div class="stats">
        <div class="count">
          <h1 class="">Total Count</h1>
          <h1 class="visible total-count">0</h1>
        </div>
        <div class="summary">
          <div class="basketball-summary">
            <div class="individual-count">
              <h1>0</h1>
            </div>
            <div class="individual-nominee">
              <h2>Basketball</h2>
            </div>
          </div>
          <div class="cricket-summary">
            <div class="individual-count">
              <h1>0</h1>
            </div>
            <div class="individual-nominee">
              <h2>Cricket</h2>
            </div>
          </div>
          <div class="football-summary">
            <div class="individual-count">
              <h1>0</h1>
            </div>
            <div class="individual-nominee">
              <h2>Football</h2>
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
  <script type="text/javascript" src="//media.twiliocdn.com/sdk/js/sync/v0.12/twilio-sync.min.js"></script>
  <script src="/javascripts/script.js"></script>
</body>

</html>

Le HTML ci-dessus contient trois balises de script. Les deux premiers scripts incluent chart.js et la bibliothèque client Twilio Sync JavaScript, via CDN. La dernière renvoie à un fichier JavaScript que vous créerez ultérieurement.

Le HTML nécessitera une feuille de style pour que le rendu s'effectue comme prévu. Puisqu'il est assez long, visitez le repo associé à ce post et copiez le contenu brut du fichier, puis collez-le dans public/stylesheets/style.css en guise de remplacement du contenu existant.

Pour finir, créez un fichier script.js dans le répertoire public/javascripts. Dans le fichier script.js, ajoutez le code suivant :

class VoteChart {
  constructor() {
    // data values for chart
    this.chartData = [0, 0, 0];
    // instantiate a new chart
    this.chart = this.barChart();
  }

  // method to create a new chart
  barChart() {
    let context = document.getElementById("voteChart").getContext("2d");

    return new Chart(context, {
      type: "bar",
      data: {
        labels: ["Basketball", "Cricket", "Football"],
        datasets: [
          {
            label: "Count",
            data: this.chartData,
            backgroundColor: [
              "rgba(255, 99, 132, 0.2)",
              "rgba(54, 162, 235, 0.2)",
              "rgba(255, 206, 86, 0.2)"
            ],
            borderColor: [
              "rgba(255, 99, 132, 1)",
              "rgba(54, 162, 235, 1)",
              "rgba(255, 206, 86, 1)"
            ],
            borderWidth: 1,
            barPercentage: 0.4
          }
        ]
      },
      options: {
        scales: {
          yAxes: [
            {
              ticks: {
                beginAtZero: true
              }
            }
          ]
        }
      }
    });
  }

  // method to destroy and re-render chart with new values
  updateChart(data) {
    this.chart.destroy();
    this.chartData = Object.values(data);
    this.barChart();
  }
}

// instantiate chart
let bchart = new VoteChart();

// function to update stats and summary on page
const updateSummaryStats = data => {
  const totalCountElement = document.querySelector(".total-count");
  const chartData = Object.values(data);

  const totalCount = chartData.reduce((acc, curr) => acc + curr, 0);
  totalCountElement.innerText = totalCount;

  for (const item in data) {
    const parent = document.querySelector(`.${item}-summary`);
    const element = parent.querySelector("h1");
    element.innerText = data[item];
  }
};

Le code ci-dessus contient et instancie une classe VoteChart possédant deux méthodes : une méthode barChart qui affiche un histogramme sur la page, et une méthode updateChart qui affiche un nouveau graphique avec des valeurs mises à jour.

Le code contient également une fonction updateSummaryStats qui met à jour le résumé des statistiques sur la page.

Pour afficher la nouvelle page d'accueil de l'application d'enquête sportive, redémarrez votre serveur de développement et rendez-vous sur http://localhost:3000/ depuis votre navigateur. Vous devriez voir une page mise à jour avec un formulaire d'enquête, un graphique et une section de résumé.

Soumission de l'enquête

La page d'accueil de l'enquête est prête, mais le bouton de soumission ne déclenche aucune action pour le moment. Chaque fois que quelqu'un clique sur le bouton de soumission, nous voulons qu'une requête soit envoyée au serveur avec l'option sélectionnée et que le graphique et les statistiques soient mis à jour selon le résultat en cours de l'enquête.

Ajoutez le code suivant à la fin du fichier script.js :

// handle form submission
const form = document.querySelector("form");
form.onsubmit = event => {
  event.preventDefault();
  const radioButtons = form.querySelectorAll("input[type=radio]");
  let checkedOption;

  radioButtons.forEach(button => {
    if (button.checked) {
      checkedOption = button.id;
    }
  });

  fetch("/users", {
    method: "POST",
    body: JSON.stringify({ [checkedOption]: checkedOption }),
    headers: {
      "Content-Type": "application/json"
    }
  })
    .then(response => response.json())
    .then(response => {
      form.reset();
      bchart.updateChart(response);
      updateSummaryStats(response);
    });
};

Dans le code ci-dessus, l'option d'enquête sélectionnée est affectée à une variable, et une requête POST est effectuée vers la route users avec comme charge utile la valeur de la variable checkedOption. La réponse sert à mettre à jour aussi bien le graphique que le résumé des statistiques.

Une fois cette option en place, l'application d'enquête sera prête à fonctionner dès que le serveur pourra traiter les requêtes et y répondre.

Gestion des résultats de l'enquête

Le formulaire d'enquête lance une requête POST vers la route users et attend une réponse qu'il utilisera pour mettre à jour les données sur le front-end.

Pour gérer la demande de formulaire, mettez à jour le fichier users.js situé dans routes/users.js en remplaçant le contenu existant par le code suivant :

const express = require("express");
const router = express.Router();

const voteCount = {
  basketball: 0,
  cricket: 0,
  football: 0
};

/* POST handle survey votes. */
router.post("/", function(req, res, next) {
  const key = Object.keys(req.body)[0];
  voteCount[key]++;
  res.status(200).send(voteCount);
});

module.exports = router;

Le code ci-dessus instancie un objet voteCount avec les trois options d'enquête en tant que clés, et définit leurs valeurs initiales sur zéro. La route users obtient la charge utile du corps de la requête et met à jour le nombre de l'option d'enquête fournie, puis renvoie l'objet voteCount mis à jour en réponse.

L'application d'enquête est ainsi entièrement fonctionnelle. Pour la tester, redémarrez votre serveur de développement et rendez-vous sur http://localhost:3000/ depuis votre navigateur. Vous devriez être en mesure de soumettre une opinion et de voir les données sur la page mise à jour avec votre sélection.

Connexion de l'application à Twilio Sync

L'application d'enquête sportive est actuellement en mesure d'accepter les choix de l'enquête et de mettre à jour la page en fonction des résultats de l'enquête. Cependant, elle ne le fait pas en temps réel, si bien que les autres utilisateurs doivent constamment actualiser l'application pour voir les résultats mis à jour.

Pour changer cela, connectez l'état de l'application (résultat de l'enquête) au service Twilio Sync à l'aide d'un objet document Sync. À chaque fois qu'un nouveau choix d'enquête sera soumis, le résultat de l'enquête sera mis à jour et publié sur le service Sync. Tout client abonné à l'objet document Sync recevra en temps réel le résultat de l'enquête mis à jour.

Pour ce faire, créez un service Sync instancié à chaque fois que l'application démarre, et créez un objet document Sync qui permettra de stocker l'état de l'application. En outre, vous aurez besoin d'un token d'accès de synchronisation pour que les clients puissent s'abonner au service Sync.

Avant de créer le service de synchronisation, ajoutez la ligne de code suivante en haut de votre fichier app.js situé dans le répertoire racine du projet :

require("dotenv").config();

La ligne de code ci-dessus rend vos variables d'environnement accessibles sur le serveur.

Pour créer le service Sync, créez un fichier syncService.js dans le répertoire racine du projet, puis ajoutez-y le code suivant :

const Twilio = require("twilio");
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const syncServiceSid = process.env.TWILIO_SYNC_SERVICE_SID || "default";

const client = new Twilio(accountSid, authToken);

// create a Sync service
const service = client.sync.services(syncServiceSid);

module.exports = service;

Dans le code ci-dessus, nous avons importé la bibliothèque Twilio, ainsi que nos Account SID Twilio et Auth Token. Un objet client Twilio est instancié et utilisé pour créer un service de synchronisation qui est ensuite exporté.

Pour utiliser le service Sync ci-dessus à chaque démarrage de l'application, mettez à jour le code dans votre fichier index.js situé dans routes/index.js en remplaçant le contenu par le code suivant :

const express = require("express");
const router = express.Router();

const Twilio = require("twilio");
const syncService = require("../syncService");
const AccessToken = Twilio.jwt.AccessToken;
const SyncGrant = AccessToken.SyncGrant;
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const apiKey = process.env.TWILIO_API_KEY;
const apiSecret = process.env.TWILIO_API_SECRET;
const syncServiceSid = process.env.TWILIO_SYNC_SERVICE_SID || "default";

// create a document resource, providing it a Sync service resource SID
syncService.documents
  .create({
    uniqueName: "SportsPoll",
    data: {
      basketball: 0,
      cricket: 0,
      football: 0
    }
  })
  .then(document => console.log(document));

/* GET home page. */
router.get("/", function(req, res, next) {
  // Generate access token
  const token = new AccessToken(accountSid, apiKey, apiSecret);

  // create a random string and use as token identity
  let randomString = [...Array(10)]
    .map(_ => ((Math.random() * 36) | 0).toString(36))
    .join("");
  token.identity = randomString;

  // Point token to a particular Sync service.
  const syncGrant = new SyncGrant({
    serviceSid: syncServiceSid
  });
  token.addGrant(syncGrant);

  res.render("index", { title: "Sports Poll", token: token.toJwt() });
});

module.exports = router;

Le code ci-dessus importe le service Twilio Sync avec la bibliothèque Twilio Node Helper et vos informations d'identification Twilio. Le service Sync est utilisé pour créer un objet de document Sync avec le nom unique SportsPoll. Le document Sync reçoit une donnée initiale contenant les trois options d'enquête en tant que clés, et définit leurs valeurs initiales sur zéro.

Dans la route GET, un token d'accès Sync est généré et son identité est définie sur une chaîne aléatoire. Enfin, le token d'accès se voit autoriser l'accès au service Sync et est envoyé en tant que variable locale vers la page d'accueil.

Pour accéder au token à partir de la page d'accueil, ajoutez la ligne de code suivante au corps de votre fichier index.ejs situé dans vues/index.ejs. Le code devrait être ajouté n'importe où au-dessus de la balise de script qui renvoie au fichier script.js :

<script>let token = <%- JSON.stringify(token) %>;</script>

Remarque : le linter de votre éditeur de code risque de se plaindre de la ligne de code ci-dessus, mais c'est là une syntaxe Embedded JavaScript (EJS) valide qui génère des valeurs non échappées dans le modèle.

Une fois le service Sync et le token d'accès (Access Token) prêts, connectez le client front-end au service Sync en ajoutant le code ci-dessous au bas de votre fichier script.js :

// connect to Sync Service
let syncClient = new Twilio.Sync.Client(token, { logLevel: "info" });

syncClient.on("connectionStateChanged", state => {
  if (state != "connected") {
    console.log(`Sync not connected: ${state}`);
  } else {
    console.log("Sync is connected");
  }
});

Le code ci-dessus crée un client de synchronisation à l'aide du token d'accès déjà généré. Le client de synchronisation écoute à la recherche d'un événement connectionStateChanged et consigne l'état de la connexion à la console du navigateur.

Pour confirmer que votre client de synchronisation est connecté au service Sync, redémarrez votre serveur de développement et rendez-vous sur http://localhost:3000/ depuis votre navigateur. Si la connexion a été établie avec succès, le texte « Sync is connected » (Sync est connecté) doit apparaître dans la console Web de votre navigateur.

Mise à jour en temps réel des résultats de l'enquête

L'application d'enquête sportive est maintenant connectée au service Twilio Sync et prête à recevoir et à diffuser les mises à jour des résultats de l'enquête. Pour que chaque client connecté puisse obtenir des mises à jour en temps réel des résultats de l'enquête, le document SportsPoll est mis à jour à chaque fois qu'un nouveau résultat d'enquête est soumis et que la mise à jour est reçue sur le client et utilisée pour mettre à jour l'application.

Pour mettre à jour le document SportsPoll à chaque fois qu'un nouveau résultat d'enquête est soumis, mettez à jour votre fichier users.js en remplaçant le contenu existant par le code suivant :

const express = require("express");
const router = express.Router();

const syncService = require("../syncService");

const voteCount = {
  basketball: 0,
  cricket: 0,
  football: 0
};

/* POST handle survey votes. */
router.post("/", function(req, res, next) {
  const key = Object.keys(req.body)[0];
  voteCount[key]++;

  // update data in Sync document
  syncService
    .documents("SportsPoll")
    .update({ data: voteCount })
    .then(document => console.log(document));

  res.status(200).send(voteCount);
});

module.exports = router;

Dans le code ci-dessus, le service Sync est importé et le document SportsPoll est mis à jour avec l'objet voteCount mis à jour avant que la réponse ne soit renvoyée au client.

Pour obtenir cette mise à jour sur le client, ajoutez le code suivant au bas de votre fichier script.js :

// Open SportsPoll document
syncClient.document("SportsPoll").then(document => {
  console.log("SportsPoll document loaded");

  let data = document.value;

  //render chart with sync document data
  bchart.updateChart(data);

  // display stats and summary
  updateSummaryStats(data);

  // update chart when there's an update to the sync document
  document.on("updated", event => {
    bchart.updateChart(event.value);
    updateSummaryStats(event.value);
  });
});

Côté client, le document SportsPoll est ouvert et l'application est mise à jour avec les données initiales qu'il contient. En cas de mise à jour du document SportsPoll, les données mises à jour sont reçues et utilisées pour mettre à jour à la fois le graphique et la section des statistiques.

Comme il n'est pas nécessaire de mettre à jour l'application avec la réponse du serveur, vous pouvez supprimer deux lignes du fichier script.js. Recherchez le code de la méthode fetch indiquée ci-dessous et supprimez du code uniquement les deux lignes marquées par // remove this line :

...
fetch("/users", {
    method: "POST",
    body: JSON.stringify({ [checkedOption]: checkedOption }),
    headers: {
      "Content-Type": "application/json"
    }
  })
    .then(response => response.json())
    .then(response => {
      form.reset();
      bchart.updateChart(response); // remove this line
      updateSummaryStats(response); // remove this line
    });
...  

La suppression des lignes ci-dessus garantit que le résultat de l'enquête n'est plus mis à jour dans la réponse fetch. Veillez à ne pas supprimer l'élément }; final qui ferme la déclaration form.

Test de l'application

Pour tester la fonctionnalité de mise à jour en temps réel de l'application d'enquête sportive, ouvrez l'application dans deux fenêtres de navigateur différentes. Comme le montre la capture d'écran animée ci-dessous, lorsqu'un vote est soumis dans un navigateur, le résultat est instantanément mis à jour dans les deux navigateurs.

Les résultats de l'enquête resteront affichés sur la page même si l'application est redémarrée. Cela est rendu possible par le fait que Sync conserve les résultats, et que le document Sync est la source unique de l'état de l'application. Dans une application de production, vous voudrez stocker les données dans une couche de données persistante, telle qu'une base de données, et pouvoir faire la différence entre les utilisateurs rejoignant des enquêtes en cours et les nouvelles enquêtes.

Remarque : vous devrez peut-être modifier les paramètres de sécurité de votre navigateur pour désactiver le blocage des cookies afin de permettre le fonctionnement de la synchronisation. En cas d'avertissement de la fenêtre de la console du navigateur indiquant que l'accès au CDN Twilio a été bloqué, vous devrez modifier vos paramètres.

Résultats du test pour l&#x27;application d&#x27;enquête sportive

Résumé

Dans cet article, vous avez appris comment ajouter grâce à Twilio Sync des fonctionnalités en temps réel à une application Node.js. Sync permet de réaliser bien plus que ce qui a été montré dans ce post, car il est conçu pour fonctionner entièrement en tant que système de gestion d'état autonome, ou en association avec d'autres produits Twilio.

Ressources supplémentaires

Ces ressources vous aideront à approfondir vos connaissances des outils, des technologies et des techniques mentionnés dans ce post :

Express : l'application d'enquête sportive est construite avec Express, un « framework Web rapide, sans préjugés et minimaliste pour Node.js ».

Librairie d'aide de Twilio pour Node : la documentation de référence fournit des exemples de code qui vous montrent différentes façons d'utiliser les produits Twilio avec Node.js.

Twilio Sync : reportez-vous à cette page produit pour obtenir de plus amples informations sur le SDK Sync et l'API Sync, y compris des liens vers la documentation complète.

Vous trouverez le code complet de ce post sur GitHub.

Chuks Opia est ingénieur logiciel chez Andela. Il est également rédacteur technique et aime aider les développeurs débutants à trouver leur voie. Si vous avez des questions, n'hésitez pas à nous contacter sur Twitter : @developia_ ou GitHub : @9jaswag.