Hoe caching te implementeren in Node.js met Redis

De auteur heeft /dev/color geselecteerd om een donatie te ontvangen als onderdeel van het programma Write for DOnations.

Inleiding

De meeste applicaties zijn afhankelijk van gegevens, of ze nu afkomstig zijn van een database of een API. Gegevens ophalen uit een API stuurt een netwerkverzoek naar de API-server en retourneert de gegevens als reactie. Deze heen-en-weer reizen kosten tijd en kunnen de responstijd van uw applicatie naar gebruikers verhogen. Bovendien beperken de meeste API’s het aantal verzoeken dat ze binnen een specifieke tijdsperiode aan een applicatie kunnen bedienen, een proces dat bekend staat als rate limiting.

Om deze problemen te omzeilen, kunt u uw gegevens in de cache opslaan, zodat de applicatie slechts één verzoek naar een API maakt, en alle daaropvolgende gegevensverzoeken de gegevens uit de cache ophalen. Redis, een in-memory database die gegevens in het servergeheugen opslaat, is een populair hulpmiddel om gegevens te cachen. U kunt verbinding maken met Redis in Node.js met behulp van de node-redis module, die u methoden geeft om gegevens op te halen en op te slaan in Redis.

In deze tutorial bouw je een Express-applicatie die gegevens ophaalt van een RESTful API met behulp van de axios-module. Vervolgens pas je de app aan om de gegevens die van de API zijn opgehaald op te slaan in Redis met behulp van de node-redis-module. Daarna implementeer je de geldigheidsperiode van de cache zodat de cache kan verlopen nadat een bepaalde hoeveelheid tijd is verstreken. Tot slot gebruik je de Express-middleware om gegevens te cachen.

Vereisten

Om de tutorial te volgen, heb je het volgende nodig:

Stap 1 — Het Opzetten van het Project

In deze stap zul je de benodigde afhankelijkheden voor dit project installeren en een Express server starten. In deze tutorial zul je een wiki maken met informatie over verschillende soorten vissen. We zullen het project fish_wiki noemen.

Eerst, maak de directory voor het project aan met het mkdir commando:

  1. mkdir fish_wiki

Ga naar de directory:

  1. cd fish_wiki

Initialiseer het package.json bestand met behulp van het npm commando:

  1. npm init -y

De -y optie accepteert automatisch alle standaardwaarden.

Wanneer je het npm init commando uitvoert, zal het het package.json bestand aanmaken in je map met de volgende inhoud:

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" }

Vervolgens zal je de volgende pakketten installeren:

  • express: een webserver framework voor Node.js.
  • axios: een Node.js HTTP-client, die handig is voor het maken van API-oproepen.
  • node-redis: een Redis-client waarmee je gegevens in Redis kunt opslaan en benaderen.

Om de drie pakketten samen te installeren, voer je het volgende commando in:

  1. npm install express axios redis

Na het installeren van de pakketten, zal je een basis Express-server aanmaken.

Gebruik nano of de teksteditor van je keuze om het server.js bestand aan te maken en te openen:

  1. nano server.js

In je server.js bestand voer je de volgende code in om een Express-server aan te maken:

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}`);
});

Eerst importeer je express in het bestand. In de tweede regel stel je de app variabele in als een instantie van express, wat je toegang geeft tot methoden zoals get, post, listen, en nog veel meer. Deze tutorial zal zich concentreren op de get en listen methoden.

In de volgende regel definieer en wijs je de port variabele toe aan het poortnummer waarop je wilt dat de server luistert. Als er geen poortnummer beschikbaar is in een omgevingsvariabelenbestand, zal poort 3000 als standaard worden gebruikt.

Eindelijk, met behulp van de variabele app, roep je de listen()-methode van de express-module aan om de server te starten op poort 3000.

Sla het bestand op en sluit het.

Voer het bestand server.js uit met het node-commando om de server te starten:

  1. node server.js

De console zal een bericht vergelijkbaar met het volgende weergeven:

Output
App listening on port 3000

De uitvoer bevestigt dat de server draait en klaar is om elk verzoek op poort 3000 te bedienen. Omdat Node.js de server niet automatisch opnieuw laadt wanneer bestanden worden gewijzigd, stop je nu de server met CTRL+C zodat je server.js kunt bijwerken in de volgende stap.

Zodra je de afhankelijkheden hebt geïnstalleerd en een Express-server hebt gemaakt, zul je gegevens ophalen uit een RESTful API.

Stap 2 — Gegevens ophalen uit een RESTful API zonder caching

In deze stap zul je voortbouwen op de Express-server uit de vorige stap om gegevens op te halen uit een RESTful API zonder caching te implementeren, waarbij wordt gedemonstreerd wat er gebeurt wanneer gegevens niet in een cache worden opgeslagen.

Om te beginnen, open je het bestand server.js in je teksteditor:

  1. nano server.js

Vervolgens haal je gegevens op uit de FishWatch API. De FishWatch API retourneert informatie over vissoorten.

In je server.js bestand, definieer een functie die API-gegevens opvraagt met de volgende gemarkeerde code:

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}`);
});

In de tweede regel importeer je de axios module. Vervolgens definieer je een asynchrone functie fetchApiData(), die species als parameter gebruikt. Om de functie asynchroon te maken, prefix je deze met het async trefwoord.

Binnen de functie roep je de axios module’s get() methode aan met het API-eindpunt waarvan je wilt dat de methode de gegevens ophaalt, dat is de FishWatch API in dit voorbeeld. Omdat de get() methode een promise implementeert, prefix je deze met het await trefwoord om de promise op te lossen. Zodra de promise is opgelost en de gegevens van de API zijn geretourneerd, roep je de console.log() methode aan. De console.log() methode zal een bericht loggen waarin staat dat er een verzoek naar de API is verzonden. Ten slotte retourneer je de gegevens van de API.

Vervolgens zal je een Express-route definiëren die GET-verzoeken accepteert. In je server.js bestand, definieer de route met de volgende code:

fish_wiki/server.js
...

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

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

In het voorgaande codeblok roep je de express module’s get() methode aan, die alleen luistert naar GET-verzoeken. De methode neemt twee argumenten:

  • /vis/:soort: het eindpunt waar Express naar zal luisteren. Het eindpunt neemt een route-parameter :soort die alles vastlegt wat op die positie in de URL is ingevoerd.
  • getSoortGegevens()(nog niet gedefinieerd): een terugroepfunctie die zal worden aangeroepen wanneer de URL overeenkomt met het gespecificeerde eindpunt in het eerste argument.

Nu de route is gedefinieerd, specificeer de getSoortGegevens terugroepfunctie:

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

De getSoortGegevens functie is een asynchrone handler-functie die als tweede argument wordt doorgegeven aan de express module’s get() methode. De getSoortGegevens() functie neemt twee argumenten: een verzoekobject en een antwoordobject. Het verzoekobject bevat informatie over de client, terwijl het antwoordobject de informatie bevat die vanuit Express naar de client wordt verzonden.

Voeg vervolgens de gemarkeerde code toe om fetchApiGegevens() aan te roepen om gegevens uit een API op te halen in de getSoortGegevens() terugroepfunctie:

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

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

In de functie haal je de waarde op die is vastgelegd vanaf het eindpunt dat is opgeslagen in het req.params object, vervolgens wijs je het toe aan de soort variabele. In de volgende regel definieer je de variabele resultaten en stel je deze in op niet gedefinieerd.

Na dat roep je de fetchApiData()-functie aan met de species-variabele als argument. Het fetchApiData()-functie-oproep is voorafgegaan door de await-syntax omdat het een promise retourneert. Wanneer de promise opgelost is, retourneert het de data, die dan wordt toegewezen aan de results-variabele.

Vervolgens voeg je de gemarkeerde code toe om runtimefouten af te handelen:

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");
  }
}
...

Je definieert het try/catch-blok om runtimefouten af te handelen. In het try-blok roep je fetchApiData() aan om data op te halen uit een API.
Als er een fout optreedt, logt het catch-blok de fout en retourneert het een 404-statuscode met een “Data niet beschikbaar” reactie.

De meeste API’s retourneren een 404-statuscode wanneer ze geen gegevens hebben voor een specifieke query, wat automatisch het catch-blok activeert om uit te voeren. Echter, de FishWatch API retourneert een 200-statuscode met een lege array wanneer er geen gegevens zijn voor die specifieke query. Een 200-statuscode betekent dat het verzoek succesvol was, dus het catch()-blok wordt nooit geactiveerd.

Om het catch()-blok te activeren, moet je controleren of de array leeg is en een foutmelding genereren wanneer de if-voorwaarde waar is. Wanneer de if-voorwaarden vals evalueren, kun je een reactie naar de client sturen met de data.

Om dat te doen, voeg je de gemarkeerde code toe:

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");
  }
}
...

Zodra de gegevens zijn geretourneerd vanuit de API, controleert de if-verklaring of de results-variabele leeg is. Als aan de voorwaarde is voldaan, gebruik je de throw-verklaring om een aangepaste fout te gooien met de melding API heeft een lege array geretourneerd. Nadat deze is uitgevoerd, schakelt de uitvoering over naar het catch-blok, dat het foutbericht registreert en een 404-reactie retourneert.

Aan de andere kant, als de results-variabele gegevens bevat, zal de voorwaarde van de if-verklaring niet worden vervuld. Als gevolg hiervan zal het programma het if-blok overslaan en de send-methode van het responsobject uitvoeren, die een reactie naar de client stuurt.

De send-methode neemt een object aan met de volgende eigenschappen:

  • fromCache: de eigenschap accepteert een waarde waarmee je kunt weten of de gegevens afkomstig zijn van de Redis-cache of de API. Je hebt nu een false-waarde toegewezen omdat de gegevens afkomstig zijn van een API.

  • data: de eigenschap krijgt de results-variabele toegewezen die de gegevens bevat die zijn geretourneerd door de API.

Op dit punt zal je complete code er zo uitzien:

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}`);
});

Nu alles op zijn plaats staat, sla je het bestand op en sluit je het af.

Start de express-server:

  1. node server.js

De Fishwatch API accepteert veel soorten, maar we zullen alleen de red-snapper vissoort gebruiken als een routeparameter op het eindpunt dat je gedurende deze tutorial zult testen.

Start nu je favoriete webbrowser op je lokale computer. Ga naar de URL http://localhost:3000/fish/red-snapper.

Opmerking: Als je de tutorial op een externe server volgt, kun je de app in je browser bekijken met behulp van poortdoorsluizing.

Met de Node.js-server nog steeds actief, open je een ander terminalvenster op je lokale computer en voer je het volgende commando in:

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

Na verbinding te hebben gemaakt met de server, ga je naar http://localhost:3000/fish/red-snapper in de webbrowser op je lokale machine.

Zodra de pagina is geladen, zou je moeten zien dat fromCache is ingesteld op false.

Nu, vernieuw de URL nog drie keer en kijk naar je terminal. De terminal zal “Request sent to the API” loggen zo vaak als je je browser hebt vernieuwd.

Als je de URL drie keer hebt vernieuwd na het eerste bezoek, zal je output er als volgt uitzien:

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

Deze output toont aan dat er telkens wanneer je de browser vernieuwt, een netwerkverzoek naar de API-server wordt verzonden. Als je een applicatie had met 1000 gebruikers die hetzelfde eindpunt raken, zijn dat 1000 netwerkverzoeken naar de API.

Wanneer je caching implementeert, wordt het verzoek naar de API slechts één keer uitgevoerd. Alle daaropvolgende verzoeken halen gegevens op uit de cache, waardoor de prestaties van je applicatie worden verbeterd.

Voor nu, stop je Express-server met CTRL+C.

Nu je gegevens van een API kunt opvragen en aan gebruikers kunt serveren, ga je gegevens die van een API zijn teruggegeven cachen in Redis.

Stap 3 — Cachen van RESTful API-verzoeken met behulp van Redis

In dit gedeelte ga je gegevens van de API cachen, zodat alleen het eerste bezoek aan het eindpunt van je app gegevens van een API-server zal opvragen, en alle volgende verzoeken gegevens zullen ophalen uit de Redis-cache.

Open het bestand server.js:

  1. nano server.js

In je server.js-bestand, importeer de node-redis-module:

fish_wiki/server.js

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

In hetzelfde bestand, maak verbinding met Redis met behulp van de node-redis-module door de gemarkeerde code toe te voegen:

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) {
  ...
}
...

Eerst definieer je de variabele redisClient met de waarde ingesteld op undefined. Daarna definieer je een anonieme zelf-opgeroepen asynchrone functie, wat een functie is die direct wordt uitgevoerd nadat deze is gedefinieerd. Je definieert een anonieme zelf-opgeroepen asynchrone functie door een naamloze functiedefinitie tussen haakjes te plaatsen (async () => {...}). Om het zelf-opgeroepen te maken, volg je het onmiddellijk op met een andere set haakjes (), waardoor het eruitziet als (async () => {...})().

Binnen de functie roep je de createClient()-methode van de redis-module aan die een redis-object aanmaakt. Aangezien je niet de poort voor Redis hebt opgegeven toen je de createClient()-methode aanriep, zal Redis poort 6379, de standaardpoort, gebruiken.

Je roept ook de on()-methode van Node.js aan die gebeurtenissen op het Redis-object registreert. De on()-methode neemt twee argumenten: error en een callback. Het eerste argument error is een gebeurtenis die wordt geactiveerd wanneer Redis een fout tegenkomt. Het tweede argument is een callback die wordt uitgevoerd wanneer de error-gebeurtenis wordt afgevuurd. De callback logt de fout in de console.

Tenslotte roep je de connect()-methode aan, die de verbinding met Redis op de standaardpoort 6379 start. De connect()-methode retourneert een promise, dus je voorziet het van de await-syntax om het op te lossen.

Nu je applicatie verbonden is met Redis, zal je de getSpeciesData()-callback aanpassen om data in Redis op te slaan bij het eerste bezoek en de data uit de cache op te halen voor alle volgende verzoeken.

Voeg in je server.js-bestand de gemarkeerde code toe en werk deze bij:

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) {
    ...
  }
}
...

In de functie getSpeciesData, definieer je de variabele isCached met de waarde false. Binnen de try-block roep je de get()-methode van de module node-redis aan met species als argument. Wanneer de methode de sleutel in Redis vindt die overeenkomt met de waarde van de variabele species, retourneert het de gegevens, die vervolgens aan de variabele cacheResults worden toegewezen.

Vervolgens controleert een if-verklaring of de variabele cacheResults gegevens bevat. Als de voorwaarde is voldaan, wordt de variabele isCached ingesteld op true. Hierna roep je de parse()-methode van het JSON-object aan met cacheResults als argument. De parse()-methode converteert JSON-tekstgegevens naar een JavaScript-object. Nadat de JSON is geparseerd, roep je de send()-methode aan, die een object gebruikt met de eigenschap fromCache ingesteld op de variabele isCached. De methode stuurt het antwoord naar de client.

Als de get()-methode van de module node-redis geen gegevens in de cache vindt, wordt de variabele cacheResults ingesteld op null. Als gevolg hiervan evalueert de if-verklaring naar false. Wanneer dat gebeurt, wordt de uitvoering overgeslagen naar het else-blok waarin je de functie fetchApiData() aanroept om gegevens op te halen van de API. Echter, zodra de gegevens van de API zijn geretourneerd, worden deze niet opgeslagen in Redis.

Om de gegevens op te slaan in de Redis-cache, moet je de set()-methode van de module node-redis gebruiken om deze op te slaan. Voeg hiervoor de gemarkeerde regel toe:

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) {
    ...
  }
}
...

Binnen het else-blok, nadat de gegevens zijn opgehaald, roep je de set()-methode van de node-redis-module aan om de gegevens op te slaan in Redis onder de sleutelnaam van de waarde in de species-variabele.

De set()-methode neemt twee argumenten, die key-value-paren zijn: species en JSON.stringify(results).

Het eerste argument, species, is de sleutel waar de gegevens onder opgeslagen zullen worden in Redis. Onthoud dat species ingesteld is op de waarde die doorgegeven is aan het eindpunt dat je hebt gedefinieerd. Bijvoorbeeld, wanneer je /fish/red-snapper bezoekt, wordt species ingesteld op red-snapper, wat de sleutel zal zijn in Redis.

Het tweede argument, JSON.stringify(results), is de waarde voor de sleutel. In het tweede argument roep je de stringify()-methode van JSON aan met de results-variabele als argument, die gegevens bevat die teruggegeven zijn door de API. De methode zet JSON om in een tekenreeks; daarom heb je bij het ophalen van gegevens uit de cache met behulp van de get()-methode van de node-redis-module eerder de JSON.parse-methode aangeroepen met de cacheResults-variabele als argument.

Je complete bestand ziet er nu als volgt uit:

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}`);
});

Sla op en verlaat je bestand, en voer het bestand server.js uit met het node-commando:

  1. node server.js

Zodra de server is gestart, vernieuw http://localhost:3000/fish/red-snapper in je browser.

Merk op dat fromCache nog steeds is ingesteld op false:

Vernieuw de pagina opnieuw om te zien dat deze keer fromCache is ingesteld op true:

Vernieuw de pagina vijf keer en ga terug naar de terminal. Je output zal er ongeveer als volgt uitzien:

Output
App listening on port 3000 Request sent to the API

Nu is Request sent to the API slechts één keer gelogd na meerdere URL-vernieuwingen, in tegenstelling tot het laatste gedeelte waar het bericht werd gelogd voor elke vernieuwing. Deze output bevestigt dat er slechts één verzoek naar de server is verzonden en dat vervolgens gegevens uit Redis worden opgehaald.

Om verder te bevestigen dat de gegevens zijn opgeslagen in Redis, stop je server met behulp van CTRL+C. Maak verbinding met de Redis-serverclient met het volgende commando:

  1. redis-cli

Haal de gegevens op onder de sleutel red-snapper:

  1. get red-snapper

Je output zal lijken op het volgende (bewerkt voor beknoptheid):

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

De output toont de gestringificeerde versie van JSON-gegevens die de API retourneert wanneer je de /fish/red-snapper-eindpunt bezoekt, wat bevestigt dat de API-gegevens zijn opgeslagen in de Redis-cache.

Verlaat de Redis Server-client:

  1. exit

Nu je gegevens van een API kunt cachen, kun je ook de geldigheid van de cache instellen.

Stap 4 — Implementatie van Cache-geldigheid

Bij het cachen van gegevens moet je weten hoe vaak de gegevens veranderen. Sommige API-gegevens veranderen binnen enkele minuten; andere binnen uren, weken, maanden of jaren. Door een geschikte cache-duur in te stellen, zorg je ervoor dat je toepassing actuele gegevens aan je gebruikers levert.

In deze stap stel je de cache-geldigheid in voor de API-gegevens die in Redis moeten worden opgeslagen. Wanneer de cache verloopt, zal je toepassing een verzoek naar de API sturen om recente gegevens op te halen.

Je moet de documentatie van je API raadplegen om de juiste vervaltijd voor de cache in te stellen. De meeste documentatie vermeldt hoe vaak de gegevens worden bijgewerkt. Er zijn echter gevallen waarin de documentatie deze informatie niet verstrekt, dus moet je misschien raden. Het controleren van de eigenschap last_updated van verschillende API-eindpunten kan laten zien hoe vaak de gegevens worden bijgewerkt.

Zodra je de cache-duur hebt gekozen, moet je deze omzetten naar seconden. Voor de demonstratie in deze tutorial stel je de cache-duur in op 3 minuten of 180 seconden. Deze voorbeeldduur maakt het testen van de functionaliteit van de cache-duur gemakkelijker.

Om de duur van de cache-geldigheid te implementeren, open het bestand server.js:

  1. nano server.js

Voeg de gemarkeerde code toe:

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}`);
});

In de set()-methode van de module node-redis geef je een derde argument door in de vorm van een object met de volgende eigenschappen:

  • EX: accepteert een waarde met de cache-duur in seconden.
  • NX: wanneer ingesteld op true, zorgt het ervoor dat de set()-methode alleen een sleutel instelt die nog niet bestaat in Redis.

Sla je bestand op en sluit het af.

Ga terug naar de Redis-serverclient om de cachegeldigheid te testen:

  1. redis-cli

Verwijder de sleutel red-snapper in Redis:

  1. del red-snapper

Sluit de Redis-client af:

  1. exit

Start nu de ontwikkelingsserver met het node-commando:

  1. node server.js

Wissel terug naar je browser en vernieuw de URL http://localhost:3000/fish/red-snapper. Voor de komende drie minuten, als je de URL vernieuwt, zou de uitvoer in de terminal consistent moeten zijn met de volgende uitvoer:

Output
App listening on port 3000 Request sent to the API

Na drie minuten vernieuw je de URL in je browser. In de terminal zie je dat “Verzoek naar de API verzonden” twee keer is gelogd.

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

Deze uitvoer toont aan dat de cache is verlopen en er opnieuw een verzoek naar de API is gedaan.

Je kunt de Express-server stoppen.

Nu je de cachegeldigheid kunt instellen, ga je de gegevens cache met middleware gebruiken.

Stap 5 — Gegevens Cachen in Middleware

In deze stap ga je de Express-middleware gebruiken om gegevens te cachen. Middleware is een functie die toegang kan krijgen tot het verzoekobject, het antwoordobject en een callback die moet worden uitgevoerd nadat het is uitgevoerd. De functie die na de middleware wordt uitgevoerd, heeft ook toegang tot het verzoek- en antwoordobject. Bij gebruik van middleware kun je het verzoek- en antwoordobject aanpassen of eerder een antwoord aan de gebruiker retourneren.

Om middleware in uw applicatie voor caching te gebruiken, wijzigt u de getSpeciesData()-handlerfunctie om gegevens op te halen uit een API en deze op te slaan in Redis. U verplaatst alle code die naar gegevens zoekt in Redis naar de cacheData-middlewarefunctie.

Wanneer u het eindpunt /fish/:species bezoekt, wordt de middlewarefunctie eerst uitgevoerd om te zoeken naar gegevens in de cache; als deze worden gevonden, wordt er een reactie geretourneerd en wordt de functie getSpeciesData niet uitgevoerd. Als de middleware echter de gegevens niet in de cache vindt, roept deze de functie getSpeciesData aan om gegevens uit de API op te halen en op te slaan in Redis.

Eerst opent u uw server.js:

  1. nano server.js

Vervolgens verwijdert u de gemarkeerde code:

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");
  }
}
...

In de getSpeciesData()-functie verwijdert u alle code die zoekt naar gegevens die zijn opgeslagen in Redis. U verwijdert ook de variabele isCached, aangezien de functie getSpeciesData() alleen gegevens uit de API zal ophalen en in Redis zal opslaan.

Nadat de code is verwijderd, stelt u fromCache in op false, zoals hieronder gemarkeerd, zodat de functie getSpeciesData() er als volgt uitziet:

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");
  }
}
...

De functie getSpeciesData() haalt de gegevens op uit de API, slaat ze op in de cache en retourneert een reactie naar de gebruiker.

Voeg vervolgens de volgende code toe om de middlewarefunctie te definiëren voor het cachen van gegevens in 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) {
...
}
...

De cacheData() middleware functie heeft drie argumenten: req, res, en next. In het try block controleert de functie of de waarde in de species variabele gegevens heeft opgeslagen in Redis onder zijn sleutel. Als de gegevens in Redis staan, worden ze geretourneerd en ingesteld op de cacheResults variabele.

Vervolgens controleert de if verklaring of cacheResults gegevens bevat. De gegevens worden opgeslagen in de results variabele als deze waar is. Daarna gebruikt de middleware de send() methode om een object terug te geven met de eigenschappen fromCache ingesteld op true en data ingesteld op de results variabele.

Echter, als de if verklaring als onwaar wordt geëvalueerd, wordt de uitvoering overgeschakeld naar het else block. Binnen het else block roep je next() aan, wat de controle doorgeeft aan de volgende functie die moet worden uitgevoerd na deze.

Om de cacheData() middleware de controle door te laten geven aan de getSpeciesData() functie wanneer next() wordt aangeroepen, update de get() methode van de express module dienovereenkomstig:

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

De get() methode neemt nu cacheData als tweede argument, wat de middleware is die gegevens zoekt die zijn gecached in Redis en een antwoord retourneert wanneer deze worden gevonden.

Nu, wanneer u de /fish/:species-eindpunt bezoekt, wordt eerst cacheData() uitgevoerd. Als er gegevens in de cache staan, wordt het antwoord geretourneerd en eindigt de verzoek-responscyclus hier. Als er echter geen gegevens in de cache worden gevonden, wordt getSpeciesData() opgeroepen om gegevens op te halen uit de API, deze in de cache op te slaan en een antwoord terug te sturen.

Het volledige bestand ziet er nu als volgt uit:

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}`);
});

Sla op en verlaat uw bestand.

Om de caching goed te testen, kunt u de red-snapper-sleutel in Redis verwijderen. Ga daarvoor naar de Redis-client:

  1. redis-cli

Verwijder de red-snapper-sleutel:

  1. del red-snapper

Verlaat de Redis-client:

  1. exit

Voer nu het bestand server.js uit:

  1. node server.js

Zodra de server is gestart, ga terug naar de browser en bezoek opnieuw http://localhost:3000/fish/red-snapper. Vernieuw het meerdere keren.

De terminal zal het bericht loggen dat er een verzoek naar de API is verzonden. De cacheData()-middleware zal alle verzoeken voor de komende drie minuten afhandelen. Uw uitvoer ziet er ongeveer zo uit als u de URL willekeurig vaak vernieuwt binnen een tijdsbestek van vier minuten:

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

Het gedrag is consistent met hoe de applicatie werkte in de vorige sectie.

U kunt nu gegevens in Redis cachen met behulp van middleware.

Conclusie

In dit artikel heb je een applicatie gebouwd die gegevens ophaalt van een API en de gegevens als antwoord naar de client retourneert. Vervolgens heb je de app aangepast om de API-respons in Redis te cachen bij het eerste bezoek en de gegevens vanaf de cache te serveren voor alle volgende verzoeken. Je hebt de duur van die cache aangepast om te verlopen nadat een bepaalde tijd is verstreken, en vervolgens heb je middleware gebruikt om de ophaling van gecachte gegevens te verwerken.

Als volgende stap kun je de documentatie van Node Redis verkennen om meer te weten te komen over de functies die beschikbaar zijn in de node-redis-module. Je kunt ook de documentatie van Axios en Express lezen voor een dieper inzicht in de onderwerpen die in deze tutorial worden behandeld.

Om je vaardigheid in Node.js verder te ontwikkelen, bekijk Hoe te coderen in de Node.js-serie.

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