Comment mettre en cache dans Node.js en utilisant Redis

L’auteur a sélectionné /dev/color pour recevoir une donation dans le cadre du programme Écrire pour des Dons.

Introduction

La plupart des applications dépendent de données, qu’elles proviennent d’une base de données ou d’une API. Récupérer des données depuis une API envoie une requête réseau au serveur de l’API et renvoie les données en réponse. Ces allers-retours prennent du temps et peuvent augmenter le temps de réponse de votre application aux utilisateurs. De plus, la plupart des API limitent le nombre de requêtes qu’elles peuvent servir à une application dans un laps de temps spécifique, un processus connu sous le nom de limitation du débit.

Pour contourner ces problèmes, vous pouvez mettre en cache vos données afin que l’application effectue une seule requête vers une API, et toutes les requêtes de données ultérieures récupéreront les données depuis le cache. Redis, une base de données en mémoire qui stocke les données dans la mémoire du serveur, est un outil populaire pour mettre en cache les données. Vous pouvez vous connecter à Redis en Node.js en utilisant le module node-redis, qui vous donne des méthodes pour récupérer et stocker des données dans Redis.

Dans ce tutoriel, vous allez construire une application Express qui récupère des données à partir d’une API RESTful en utilisant le module axios. Ensuite, vous allez modifier l’application pour stocker les données récupérées de l’API dans Redis en utilisant le module node-redis. Après cela, vous allez implémenter la période de validité du cache afin que le cache puisse expirer après un certain laps de temps. Enfin, vous utiliserez le middleware Express pour mettre en cache les données.

Prérequis

Pour suivre le tutoriel, vous aurez besoin de :

Étape 1 — Configuration du Projet

Dans cette étape, vous installerez les dépendances nécessaires pour ce projet et démarrerez un serveur Express. Dans ce tutoriel, vous créerez un wiki contenant des informations sur différents types de poissons. Nous appellerons le projet fish_wiki.

Tout d’abord, créez le répertoire pour le projet en utilisant la commande mkdir:

  1. mkdir fish_wiki

Déplacez-vous dans le répertoire:

  1. cd fish_wiki

Initialisez le fichier package.json en utilisant la commande npm :

  1. npm init -y

L’option -y accepte automatiquement toutes les valeurs par défaut.

Lorsque vous exécutez la commande npm init, elle créera le fichier package.json dans votre répertoire avec le contenu suivant :

Output
Wrote to /home/your_username/<^>fish_wiki<^/package.json: { "name": "fish_wiki", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

Ensuite, vous installerez les packages suivants :

  • express : un cadre de serveur web pour Node.js.
  • axios : un client HTTP Node.js, utile pour effectuer des appels API.
  • node-redis : un client Redis qui vous permet de stocker et d’accéder aux données dans Redis.

Pour installer les trois packages ensemble, saisissez la commande suivante :

  1. npm install express axios redis

Après avoir installé les packages, vous créerez un serveur Express de base.

En utilisant nano ou l’éditeur de texte de votre choix, créez et ouvrez le fichier server.js :

  1. nano server.js

Dans votre fichier server.js, saisissez le code suivant pour créer un serveur Express :

fish_wiki/server.js
const express = require("express");

const app = express();
const port = process.env.PORT || 3000;


app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

Tout d’abord, importez express dans le fichier. À la deuxième ligne, vous définissez la variable app comme une instance de express, ce qui vous donne accès à des méthodes telles que get, post, listen, et bien d’autres. Ce tutoriel se concentrera sur les méthodes get et listen.

Dans la ligne suivante, vous définissez et assignez la variable port au numéro de port sur lequel vous voulez que le serveur écoute. Si aucun numéro de port n’est disponible dans un fichier de variables d’environnement, le port 3000 sera utilisé par défaut.

Enfin, en utilisant la variable app, vous invoquez la méthode listen() du module express pour démarrer le serveur sur le port 3000.

Enregistrez et fermez le fichier.

Exécutez le fichier server.js en utilisant la commande node pour démarrer le serveur :

  1. node server.js

La console affichera un message similaire à ce qui suit :

Output
App listening on port 3000

La sortie confirme que le serveur est en cours d’exécution et prêt à traiter toutes les demandes sur le port 3000. Étant donné que Node.js ne recharge pas automatiquement le serveur lorsque les fichiers sont modifiés, vous allez maintenant arrêter le serveur en utilisant CTRL+C afin de pouvoir mettre à jour server.js à l’étape suivante.

Une fois que vous avez installé les dépendances et créé un serveur Express, vous récupérerez des données à partir d’une API RESTful.

Étape 2 — Récupération de données à partir d’une API RESTful sans mise en cache

Dans cette étape, vous allez construire sur le serveur Express de l’étape précédente pour récupérer des données à partir d’une API RESTful sans implémenter de mise en cache, démontrant ce qui se passe lorsque les données ne sont pas stockées dans une cache.

Pour commencer, ouvrez le fichier server.js dans votre éditeur de texte :

  1. nano server.js

Ensuite, vous récupérerez des données à partir de l’API FishWatch. L’API FishWatch retourne des informations sur les espèces de poissons.

Dans votre fichier server.js, définissez une fonction qui demande des données d’API avec le code surligné suivant :

fish_wiki/server.js
const express = require("express");
const axios = require("axios");

const app = express();
const port = process.env.PORT || 3000;

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

Dans la deuxième ligne, vous importez le module axios. Ensuite, vous définissez une fonction asynchrone fetchApiData(), qui prend species comme paramètre. Pour rendre la fonction asynchrone, vous la préfixez avec le mot-clé async.

Dans la fonction, vous appelez la méthode get() du module axios avec l’URL de l’API à partir de laquelle vous souhaitez que la méthode récupère les données, qui est l’API FishWatch dans cet exemple. Puisque la méthode get() implémente une promesse, vous la préfixez avec le mot-clé await pour résoudre la promesse. Une fois que la promesse est résolue et que les données sont renvoyées de l’API, vous appelez la méthode console.log(). La méthode console.log() va enregistrer un message indiquant qu’une demande a été envoyée à l’API. Enfin, vous renvoyez les données de l’API.

Ensuite, vous allez définir une route Express qui accepte les requêtes GET. Dans votre fichier server.js, définissez la route avec le code suivant :

fish_wiki/server.js
...

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  ...
});

Dans le bloc de code précédent, vous appelez la méthode get() du module express, qui écoute uniquement les requêtes GET. La méthode prend deux arguments :

  • /poisson/:espèce: le point d’écoute sur lequel Express sera à l’écoute. Le point de terminaison prend un paramètre de route :espèce qui capture tout ce qui est saisi à cette position dans l’URL.
  • getSpeciesData() (pas encore défini) : une fonction de rappel qui sera appelée lorsque l’URL correspond au point de terminaison spécifié dans le premier argument.

Maintenant que la route est définie, spécifiez la fonction de rappel getSpeciesData:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
}
app.get("/fish/:species", getSpeciesData);
...

La fonction getSpeciesData est une fonction de gestion asynchrone passée à la méthode get() du module express en tant que deuxième argument. La fonction getSpeciesData() prend deux arguments : un objet de demande et un objet de réponse. L’objet de demande contient des informations sur le client, tandis que l’objet de réponse contient les informations qui seront envoyées au client depuis Express.

Ensuite, ajoutez le code en surbrillance pour appeler fetchApiData() afin de récupérer des données à partir d’une API dans la fonction de rappel getSpeciesData():

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  results = await fetchApiData(species);
}
...

Dans la fonction, vous extrayez la valeur capturée à partir du point de terminaison stocké dans l’objet req.params, puis vous l’assignez à la variable species. À la ligne suivante, vous définissez la variable results et la définissez sur undefined.

Après cela, vous invoquez la fonction fetchApiData() avec la variable species comme argument. L’appel de la fonction fetchApiData() est préfixé avec la syntaxe await car elle retourne une promesse. Lorsque la promesse est résolue, elle retourne les données, qui sont ensuite assignées à la variable results.

Ensuite, ajoutez le code surligné pour gérer les erreurs d’exécution :

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

Vous définissez le try/catch bloc pour gérer les erreurs d’exécution. Dans le bloc try, vous appelez fetchApiData() pour récupérer des données à partir d’une API. Si une erreur est rencontrée, le bloc catch enregistre l’erreur et renvoie un code d’état 404 avec une réponse « Données non disponibles ».

La plupart des APIs retournent un code d’état 404 lorsqu’elles n’ont pas de données pour une requête spécifique, ce qui déclenche automatiquement l’exécution du bloc catch. Cependant, l’API FishWatch retourne un code d’état 200 avec un tableau vide lorsqu’il n’y a pas de données pour cette requête spécifique. Un code d’état 200 signifie que la requête a réussi, donc le bloc catch() n’est jamais déclenché.

Pour déclencher le bloc catch(), vous devez vérifier si le tableau est vide et déclencher une erreur lorsque la condition if est vraie. Lorsque les conditions if s’évaluent à faux, vous pouvez envoyer une réponse au client contenant les données.

Pour ce faire, ajoutez le code surligné :

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  ...
  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

Une fois que les données sont renvoyées de l’API, l’instruction if vérifie si la variable results est vide. Si la condition est remplie, vous utilisez l’instruction throw pour déclencher une erreur personnalisée avec le message L'API a renvoyé un tableau vide. Après son exécution, le programme passe au bloc catch, qui enregistre le message d’erreur et renvoie une réponse 404.

Inversement, si la variable results contient des données, la condition de l’instruction if ne sera pas remplie. Par conséquent, le programme sautera le bloc if et exécutera la méthode send de l’objet de réponse, qui envoie une réponse au client.

La méthode send prend un objet ayant les propriétés suivantes :

  • fromCache : la propriété accepte une valeur qui vous aide à savoir si les données proviennent du cache Redis ou de l’API. Vous avez maintenant attribué une valeur false car les données proviennent d’une API.

  • data : la propriété est assignée à la variable results qui contient les données renvoyées par l’API.

À ce stade, votre code complet ressemblera à ceci :

fish_wiki/server.js

const express = require("express");
const axios = require("axios");

const app = express();
const port = process.env.PORT || 3000;

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

Maintenant que tout est en place, enregistrez et quittez votre fichier.

Démarrez le serveur Express :

  1. node server.js

L’API Fishwatch accepte de nombreuses espèces, mais nous n’utiliserons que l’espèce de poisson red-snapper en tant que paramètre de route sur le point de terminaison que vous testerez tout au long de ce tutoriel.

Maintenant, lancez votre navigateur Web préféré sur votre ordinateur local. Accédez à l’URL http://localhost:3000/fish/red-snapper.

Remarque : Si vous suivez le tutoriel sur un serveur distant, vous pouvez afficher l’application dans votre navigateur en utilisant le transfert de port.

Avec le serveur Node.js toujours en cours d’exécution, ouvrez un autre terminal sur votre ordinateur local, puis saisissez la commande suivante :

  1. ssh -L 3000:localhost:3000 your-non-root-user@yourserver-ip

Après vous être connecté au serveur, accédez à http://localhost:3000/fish/red-snapper sur le navigateur Web de votre machine locale.

Une fois la page chargée, vous devriez voir fromCache défini sur false.

Maintenant, actualisez l’URL trois fois de plus et regardez votre terminal. Le terminal enregistrera « Requête envoyée à l’API » autant de fois que vous avez actualisé votre navigateur.

Si vous avez actualisé l’URL trois fois après la visite initiale, votre sortie ressemblera à ceci :

Output
App listening on port 3000 Request sent to the API Request sent to the API Request sent to the API Request sent to the API

Cette sortie montre qu’une requête réseau est envoyée au serveur API chaque fois que vous actualisez le navigateur. Si vous aviez une application avec 1000 utilisateurs accédant au même point de terminaison, cela représenterait 1000 requêtes réseau envoyées à l’API.

Lorsque vous implémentez le cache, la requête à l’API ne sera effectuée qu’une seule fois. Toutes les requêtes ultérieures obtiendront les données du cache, ce qui améliorera les performances de votre application.

Pour l’instant, arrêtez votre serveur Express avec CTRL+C.

Maintenant que vous pouvez demander des données à une API et les fournir aux utilisateurs, vous allez mettre en cache les données renvoyées par une API dans Redis.

Étape 3 — Mise en cache des requêtes d’API RESTful à l’aide de Redis

Dans cette section, vous mettrez en cache les données de l’API afin que seule la première visite sur votre point de terminaison d’application demande des données à un serveur API, et que toutes les demandes suivantes récupèrent des données depuis le cache Redis.

Ouvrez le fichier server.js:

  1. nano server.js

Dans votre fichier server.js, importez le module node-redis:

fish_wiki/server.js

const express = require("express");
const axios = require("axios");
const redis = require("redis");
...

Dans le même fichier, connectez-vous à Redis en utilisant le module node-redis en ajoutant le code mis en évidence:

fish_wiki/server.js

const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  redisClient = redis.createClient();

  redisClient.on("error", (error) => console.error(`Error : ${error}`));

  await redisClient.connect();
})();

async function fetchApiData(species) {
  ...
}
...

Tout d’abord, vous définissez la variable redisClient avec la valeur définie sur undefined. Ensuite, vous définissez une fonction asynchrone anonyme auto-invoquée, qui est une fonction qui s’exécute immédiatement après sa définition. Vous définissez une fonction asynchrone anonyme auto-invoquée en enfermant une définition de fonction sans nom entre parenthèses (async () => {...}). Pour la rendre auto-invoquée, vous la suivez immédiatement avec un autre jeu de parenthèses (), ce qui donne (async () => {...})().

Dans la fonction, vous invoquez la méthode createClient() du module redis qui crée un objet redis. Comme vous n’avez pas fourni le port à utiliser pour Redis lors de l’invocation de la méthode createClient(), Redis utilisera le port 6379, le port par défaut.

Vous appelez également la méthode on() de Node.js qui enregistre des événements sur l’objet Redis. La méthode on() prend deux arguments : error et un rappel. Le premier argument error est un événement déclenché lorsque Redis rencontre une erreur. Le second argument est un rappel qui s’exécute lorsque l’événement error est émis. Le rappel enregistre l’erreur dans la console.

Enfin, vous appelez la méthode connect(), qui démarre la connexion avec Redis sur le port par défaut 6379. La méthode connect() renvoie une promesse, vous la préfixez donc avec la syntaxe await pour la résoudre.

Maintenant que votre application est connectée à Redis, vous modifierez le rappel getSpeciesData() pour stocker les données dans Redis lors de la visite initiale et récupérer les données du cache pour toutes les requêtes ultérieures.

Dans votre fichier server.js, ajoutez et mettez à jour le code en surbrillance :

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
     }
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    ...
  }
}
...

Dans la fonction getSpeciesData, vous définissez la variable isCached avec la valeur false. Dans le bloc try, vous appelez la méthode get() du module node-redis avec species comme argument. Lorsque la méthode trouve la clé dans Redis qui correspond à la valeur de la variable species, elle renvoie les données, qui sont ensuite assignées à la variable cacheResults.

Ensuite, une instruction if vérifie si la variable cacheResults contient des données. Si la condition est vérifiée, la variable isCache est assignée à true. Ensuite, vous invoquez la méthode parse() de l’objet JSON avec cacheResults comme argument. La méthode parse() convertit les données de chaîne JSON en objet JavaScript. Après que le JSON a été analysé, vous invoquez la méthode send(), qui prend un objet ayant la propriété fromCache définie sur la variable isCached. La méthode envoie la réponse au client.

Si la méthode get() du module node-redis ne trouve aucune donnée dans le cache, la variable cacheResults est définie sur null. Par conséquent, l’instruction if est évaluée à false. Lorsque cela se produit, l’exécution passe au bloc else où vous appelez la fonction fetchApiData() pour récupérer des données à partir de l’API. Cependant, une fois les données retournées par l’API, elles ne sont pas enregistrées dans Redis.

Pour stocker les données dans le cache Redis, vous devez utiliser la méthode set() du module node-redis pour les sauvegarder. Pour ce faire, ajoutez la ligne mise en évidence :

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results));
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    ...
  }
}
...

Dans le bloc else, une fois que les données ont été récupérées, vous appelez la méthode set() du module node-redis pour sauvegarder les données dans Redis sous le nom de clé de la valeur dans la variable species.

La méthode set() prend deux arguments, qui sont des paires clé-valeur : species et JSON.stringify(results).

Le premier argument, species, est la clé sous laquelle les données seront enregistrées dans Redis. Rappelez-vous que species est défini sur la valeur transmise à l’endpoint que vous avez défini. Par exemple, lorsque vous visitez /fish/red-snapper, species est défini sur red-snapper, qui sera la clé dans Redis.

Le deuxième argument, JSON.stringify(results), est la valeur de la clé. Dans le deuxième argument, vous invoquez la méthode stringify() de JSON avec la variable results comme argument, qui contient les données renvoyées par l’API. La méthode convertit JSON en chaîne ; c’est pourquoi, lorsque vous avez récupéré des données du cache en utilisant la méthode get() du module node-redis précédemment, vous avez invoqué la méthode JSON.parse avec la variable cacheResults comme argument.

Votre fichier complet ressemblera maintenant à ce qui suit :

fish_wiki/server.js
const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  redisClient = redis.createClient();

  redisClient.on("error", (error) => console.error(`Error : ${error}`));

  await redisClient.connect();
})();

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results));
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

Enregistrez et quittez votre fichier, et exécutez le server.js en utilisant la commande node:

  1. node server.js

Une fois que le serveur a démarré, actualisez http://localhost:3000/fish/red-snapper dans votre navigateur.

Remarquez que fromCache est toujours défini sur false:

Maintenant rafraîchissez à nouveau la page pour voir que cette fois fromCache est défini sur true :

Actualisez la page cinq fois et retournez au terminal. Votre sortie ressemblera à ce qui suit :

Output
App listening on port 3000 Request sent to the API

Maintenant, La requête envoyée à l'API n’a été journalisée qu’une seule fois après plusieurs actualisations d’URL, ce qui contraste avec la dernière section où le message était enregistré pour chaque actualisation. Cette sortie confirme qu’une seule requête a été envoyée au serveur et que par la suite, les données sont récupérées depuis Redis.

Pour confirmer davantage que les données sont stockées dans Redis, arrêtez votre serveur en utilisant CTRL+C. Connectez-vous au client du serveur Redis avec la commande suivante :

  1. redis-cli

Récupérez les données sous la clé red-snapper :

  1. get red-snapper

Votre sortie ressemblera à ce qui suit (édité pour plus de concision) :

Output
"[{\"Fishery Management\":\"<ul>\\n<li><a...3\"}]"

La sortie montre la version JSON stringify des données que l’API renvoie lorsque vous visitez l’endpoint /fish/red-snapper, ce qui confirme que les données de l’API sont stockées dans le cache Redis.

Quittez le client du serveur Redis :

  1. exit

Maintenant que vous pouvez mettre en cache des données à partir d’une API, vous pouvez également définir la validité du cache.

Étape 4 — Implémentation de la validité du cache

Lorsque vous mettez en cache des données, vous devez savoir à quelle fréquence les données changent. Certaines données d’API changent en quelques minutes ; d’autres en quelques heures, semaines, mois ou années. Définir une durée de cache appropriée garantit que votre application fournit des données à jour à vos utilisateurs.

Dans cette étape, vous définirez la validité du cache pour les données d’API qui doivent être stockées dans Redis. Lorsque le cache expire, votre application enverra une requête à l’API pour récupérer des données récentes.

Vous devez consulter la documentation de votre API pour définir le délai d’expiration correct du cache. La plupart des documentations mentionneront à quelle fréquence les données sont mises à jour. Cependant, il y a des cas où la documentation ne fournit pas l’information, donc vous devrez peut-être deviner. Vérifier la propriété last_updated de divers points de terminaison de l’API peut montrer à quelle fréquence les données sont mises à jour.

Une fois que vous avez choisi la durée du cache, vous devez la convertir en secondes. Pour la démonstration dans ce tutoriel, vous définirez la durée du cache à 3 minutes ou 180 secondes. Cette durée d’échantillon facilitera le test de la fonctionnalité de durée de cache.

Pour implémenter la durée de validité du cache, ouvrez le fichier server.js:

  1. nano server.js

Ajoutez le code surligné:

fish_wiki/server.js
const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  ...
})();

async function fetchApiData(species) {
  ...
}

async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results), {
        EX: 180,
        NX: true,
      });
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

Dans la méthode set() du module node-redis, vous passez un troisième argument sous forme d’objet avec les propriétés suivantes:

  • EX: accepte une valeur avec la durée du cache en secondes.
  • NX: lorsqu’il est défini sur true, il garantit que la méthode set() ne doit définir qu’une clé qui n’existe pas déjà dans Redis.

Enregistrez et quittez votre fichier.

Retournez au client du serveur Redis pour tester la validité du cache :

  1. redis-cli

Supprimez la clé red-snapper dans Redis :

  1. del red-snapper

Quittez le client Redis :

  1. exit

Maintenant, lancez le serveur de développement avec la commande node :

  1. node server.js

Revenez à votre navigateur et rafraîchissez l’URL http://localhost:3000/fish/red-snapper. Pendant les trois prochaines minutes, si vous actualisez l’URL, la sortie dans le terminal devrait être conforme à la sortie suivante :

Output
App listening on port 3000 Request sent to the API

Après trois minutes, rafraîchissez l’URL dans votre navigateur. Dans le terminal, vous devriez voir que « Requête envoyée à l’API » a été journalisé deux fois :

Output
App listening on port 3000 Request sent to the API Request sent to the API

Cette sortie montre que le cache a expiré et qu’une nouvelle requête à l’API a été effectuée :

Vous pouvez arrêter le serveur Express :

Maintenant que vous pouvez définir la validité du cache, vous allez mettre en cache des données en utilisant le middleware prochainement :

Étape 5 — Mise en Cache des Données dans le Middleware

Dans cette étape, vous utiliserez le middleware Express pour mettre en cache des données. Le middleware est une fonction qui peut accéder à l’objet de requête, à l’objet de réponse et à un rappel qui devrait s’exécuter après son exécution. La fonction qui s’exécute après le middleware a également accès à l’objet de requête et de réponse. Lors de l’utilisation du middleware, vous pouvez modifier les objets de requête et de réponse ou renvoyer une réponse à l’utilisateur plus tôt.

Pour utiliser un middleware dans votre application pour le caching, vous allez modifier la fonction de gestionnaire getSpeciesData() pour récupérer les données depuis une API et les stocker dans Redis. Vous déplacerez tout le code qui recherche les données dans Redis vers la fonction middleware cacheData.

Lorsque vous visitez le point de terminaison /fish/:species, la fonction middleware s’exécutera en premier pour rechercher les données dans le cache ; si elles sont trouvées, elle renverra une réponse et la fonction getSpeciesData ne s’exécutera pas. Cependant, si le middleware ne trouve pas les données dans le cache, il appellera la fonction getSpeciesData pour récupérer les données depuis l’API et les stocker dans Redis.

Tout d’abord, ouvrez votre fichier server.js:

  1. nano server.js

Ensuite, supprimez le code surligné:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;
  let isCached = false;

  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      isCached = true;
      results = JSON.parse(cacheResults);
    } else {
      results = await fetchApiData(species);
      if (results.length === 0) {
        throw "API returned an empty array";
      }
      await redisClient.set(species, JSON.stringify(results), {
        EX: 180,
        NX: true,
      });
    }

    res.send({
      fromCache: isCached,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

Dans la fonction getSpeciesData(), vous supprimez tout le code qui recherche les données stockées dans Redis. Vous supprimez également la variable isCached puisque la fonction getSpeciesData() ne récupérera les données que depuis l’API et les stockera dans Redis.

Une fois le code supprimé, définissez fromCache sur false comme surligné ci-dessous, de sorte que la fonction getSpeciesData() ressemble à ce qui suit:

fish_wiki/server.js
...
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    await redisClient.set(species, JSON.stringify(results), {
      EX: 180,
      NX: true,
    });

    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}
...

La fonction getSpeciesData() récupère les données depuis l’API, les stocke dans le cache et renvoie une réponse à l’utilisateur.

Ensuite, ajoutez le code suivant pour définir la fonction middleware permettant de mettre en cache les données dans Redis:

fish_wiki/server.js
...
async function cacheData(req, res, next) {
  const species = req.params.species;
  let results;
  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      results = JSON.parse(cacheResults);
      res.send({
        fromCache: true,
        data: results,
      });
    } else {
      next();
    }
  } catch (error) {
    console.error(error);
    res.status(404);
  }
}

async function getSpeciesData(req, res) {
...
}
...

La fonction middleware cacheData() prend trois arguments : req, res, et next. Dans le bloc try, la fonction vérifie si la valeur dans la variable species a des données stockées dans Redis sous sa clé. Si les données sont dans Redis, elles sont retournées et définies dans la variable cacheResults.

Ensuite, l’instruction if vérifie si cacheResults contient des données. Les données sont enregistrées dans la variable results si elle est évaluée à vrai. Ensuite, le middleware utilise la méthode send() pour renvoyer un objet avec les propriétés fromCache définie sur true et data définie sur la variable results.

Cependant, si l’instruction if est évaluée à faux, l’exécution passe au bloc else. À l’intérieur du bloc else, vous appelez next(), qui passe le contrôle à la prochaine fonction qui devrait s’exécuter après elle.

Pour faire en sorte que la fonction middleware cacheData() passe le contrôle à la fonction getSpeciesData() lorsque next() est invoquée, mettez à jour la méthode get() du module express comme suit :

fish_wiki/server.js
...
app.get("/fish/:species", cacheData, getSpeciesData);
...

La méthode get() prend maintenant cacheData comme deuxième argument, qui est le middleware qui recherche les données mises en cache dans Redis et renvoie une réponse lorsqu’elles sont trouvées.

Maintenant, lorsque vous visitez l’endpoint /fish/:species, cacheData() s’exécute en premier. Si les données sont mises en cache, elle renverra la réponse, et le cycle de demande-réponse se termine ici. Cependant, si aucune donnée n’est trouvée dans le cache, getSpeciesData() sera appelé pour récupérer des données depuis l’API, les stocker dans le cache et renvoyer une réponse.

Le fichier complet ressemblera maintenant à ceci :

fish_wiki/server.js

const express = require("express");
const axios = require("axios");
const redis = require("redis");

const app = express();
const port = process.env.PORT || 3000;

let redisClient;

(async () => {
  redisClient = redis.createClient();

  redisClient.on("error", (error) => console.error(`Error : ${error}`));

  await redisClient.connect();
})();

async function fetchApiData(species) {
  const apiResponse = await axios.get(
    `https://www.fishwatch.gov/api/species/${species}`
  );
  console.log("Request sent to the API");
  return apiResponse.data;
}

async function cacheData(req, res, next) {
  const species = req.params.species;
  let results;
  try {
    const cacheResults = await redisClient.get(species);
    if (cacheResults) {
      results = JSON.parse(cacheResults);
      res.send({
        fromCache: true,
        data: results,
      });
    } else {
      next();
    }
  } catch (error) {
    console.error(error);
    res.status(404);
  }
}
async function getSpeciesData(req, res) {
  const species = req.params.species;
  let results;

  try {
    results = await fetchApiData(species);
    if (results.length === 0) {
      throw "API returned an empty array";
    }
    await redisClient.set(species, JSON.stringify(results), {
      EX: 180,
      NX: true,
    });

    res.send({
      fromCache: false,
      data: results,
    });
  } catch (error) {
    console.error(error);
    res.status(404).send("Data unavailable");
  }
}

app.get("/fish/:species", cacheData, getSpeciesData);

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

Enregistrez et quittez votre fichier.

Pour tester le caching correctement, vous pouvez supprimer la clé red-snapper dans Redis. Pour ce faire, accédez au client Redis :

  1. redis-cli

Supprimez la clé red-snapper :

  1. del red-snapper

Quittez le client Redis :

  1. exit

Maintenant, exécutez le fichier server.js :

  1. node server.js

Une fois que le serveur démarre, retournez dans le navigateur et visitez à nouveau http://localhost:3000/fish/red-snapper. Actualisez-le plusieurs fois.

Le terminal affichera le message qu’une demande a été envoyée à l’API. Le middleware cacheData() servira toutes les demandes pour les trois prochaines minutes. Votre sortie ressemblera à ceci si vous actualisez aléatoirement l’URL dans une période de quatre minutes :

Output
App listening on port 3000 Request sent to the API Request sent to the API

Le comportement est cohérent avec celui de l’application dans la section précédente.

Vous pouvez désormais mettre en cache des données dans Redis en utilisant un middleware.

Conclusion

Dans cet article, vous avez construit une application qui récupère des données depuis une API et renvoie les données en réponse au client. Vous avez ensuite modifié l’application pour mettre en cache la réponse de l’API dans Redis lors de la visite initiale et servir les données depuis le cache pour toutes les requêtes ultérieures. Vous avez ajusté la durée de mise en cache pour qu’elle expire après un certain laps de temps, puis vous avez utilisé un middleware pour gérer la récupération des données mises en cache.

Comme prochaine étape, vous pouvez explorer la documentation de Node Redis pour en savoir plus sur les fonctionnalités disponibles dans le module node-redis. Vous pouvez également lire la documentation de Axios et Express pour une compréhension plus approfondie des sujets abordés dans ce tutoriel.

Pour continuer à développer vos compétences en Node.js, consultez la série Comment Coder en Node.js.

Source:
https://www.digitalocean.com/community/tutorials/how-to-implement-caching-in-node-js-using-redis