Come implementare la cache in Node.js utilizzando Redis

L’autore ha selezionato /dev/color per ricevere una donazione come parte del programma Write for DOnations.

Introduzione

La maggior parte delle applicazioni dipende dai dati, che provengano da un database o da un’API. Recuperare dati da un’API invia una richiesta di rete al server dell’API e restituisce i dati come risposta. Questi viaggi di andata e ritorno richiedono tempo e possono aumentare il tempo di risposta dell’applicazione agli utenti. Inoltre, la maggior parte delle API limita il numero di richieste che possono servire un’applicazione entro un determinato intervallo di tempo, un processo noto come limitazione del tasso.

Per aggirare questi problemi, è possibile memorizzare nella cache i propri dati in modo che l’applicazione faccia una singola richiesta a un’API e tutte le richieste di dati successive recuperino i dati dalla cache. Redis, un database in memoria che memorizza i dati nella memoria del server, è uno strumento popolare per la memorizzazione nella cache dei dati. È possibile connettersi a Redis in Node.js utilizzando il node-redis modulo, che fornisce metodi per recuperare e memorizzare dati in Redis.

In questo tutorial, costruirai un’applicazione Express che recupera dati da un’API RESTful usando il modulo axios. Successivamente, modificherai l’applicazione per memorizzare i dati ottenuti dall’API in Redis usando il modulo node-redis. Dopo di ciò, implementerai il periodo di validità della cache in modo che la cache possa scadere dopo che sia trascorso un certo periodo di tempo. Infine, utilizzerai il middleware di Express per memorizzare nella cache i dati.

Prerequisiti

Per seguire il tutorial, avrai bisogno di:

Passaggio 1 — Configurazione del Progetto

In questo passaggio, installerai le dipendenze necessarie per questo progetto e avvierai un server Express. In questo tutorial, creerai una wiki contenente informazioni su diversi tipi di pesci. Chiameremo il progetto fish_wiki.

Prima, crea la directory per il progetto utilizzando il comando mkdir:

  1. mkdir fish_wiki

Spostati nella directory:

  1. cd fish_wiki

Inizializza il file package.json utilizzando il comando npm:

  1. npm init -y

L’opzione -y accetta automaticamente tutti i valori predefiniti.

Quando esegui il comando npm init, verrà creato il file package.json nella tua directory con i seguenti contenuti:

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

Successivamente, installerai i seguenti pacchetti:

  • express: un framework server web per Node.js.
  • axios: un client HTTP Node.js, utile per effettuare chiamate API.
  • node-redis: un client Redis che ti permette di memorizzare e accedere ai dati in Redis.

Per installare i tre pacchetti insieme, inserisci il seguente comando:

  1. npm install express axios redis

Dopo aver installato i pacchetti, creerai un server Express di base.

Utilizzando nano o l’editor di testo che preferisci, crea e apri il file server.js:

  1. nano server.js

Nel tuo file server.js, inserisci il seguente codice per creare un server 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}`);
});

Innanzitutto, importa express nel file. Nella seconda riga, imposti la variabile app come istanza di express, che ti dà accesso a metodi come get, post, listen e molti altri. Questo tutorial si concentrerà sui metodi get e listen.

Nella riga successiva, definisci e assegni la variabile port al numero di porta su cui desideri che il server sia in ascolto. Se nessun numero di porta è disponibile in un file di variabili ambientali, verrà utilizzata la porta 3000 come predefinita.

Finalmente, utilizzando la variabile app, si invoca il metodo listen() del modulo express per avviare il server sulla porta 3000.

Salvare e chiudere il file.

Eseguire il file server.js utilizzando il comando node per avviare il server:

  1. node server.js

La console registrerà un messaggio simile al seguente:

Output
App listening on port 3000

L’output conferma che il server è in esecuzione e pronto a gestire qualsiasi richiesta sulla porta 3000. Poiché Node.js non ricarica automaticamente il server quando i file vengono modificati, è necessario interrompere il server utilizzando CTRL+C in modo da poter aggiornare server.js nel passaggio successivo.

Una volta installate le dipendenze e creato un server Express, si recupereranno i dati da un’API RESTful.

Passaggio 2 — Recupero dei dati da un’API RESTful senza caching

In questo passaggio, si costruirà sul server Express del passaggio precedente per recuperare i dati da un’API RESTful senza implementare il caching, dimostrando cosa succede quando i dati non sono memorizzati nella cache.

Per iniziare, aprire il file server.js nel proprio editor di testo:

  1. nano server.js

Successivamente, si recupereranno dati dall’API di FishWatch. L’API di FishWatch restituisce informazioni sulle specie di pesci.

Nel tuo file server.js, definisci una funzione che richiede i dati dell’API con il seguente codice evidenziato:

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

Nella seconda riga, importi il modulo axios. Successivamente, definisci una funzione asincrona fetchApiData(), che prende species come parametro. Per rendere la funzione asincrona, la prefissi con la parola chiave async.

All’interno della funzione, chiami il metodo get() del modulo axios con il punto finale dell’API da cui desideri che il metodo recuperi i dati, che è l’API FishWatch in questo esempio. Poiché il metodo get() implementa una promessa, lo prefissi con la parola chiave await per risolvere la promessa. Una volta che la promessa è risolta e i dati sono restituiti dall’API, chiami il metodo console.log(). Il metodo console.log() registrerà un messaggio che indica che è stata inviata una richiesta all’API. Infine, restituisci i dati dall’API.

Successivamente, definirai un percorso Express che accetta richieste GET. Nel tuo file server.js, definisci il percorso con il seguente codice:

fish_wiki/server.js
...

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

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

Nel blocco di codice precedente, invochi il metodo get() del modulo express, che ascolta solo le richieste GET. Il metodo prende due argomenti:

  • /fish/:species: il punto finale su cui Express sarà in ascolto. Il punto finale prende un parametro di percorso :species che cattura qualsiasi cosa inserita in quella posizione nell’URL.
  • getSpeciesData() (non ancora definito): una funzione di callback che verrà chiamata quando l’URL corrisponde al punto finale specificato nel primo argomento.

Ora che il percorso è definito, specifica la funzione di callback getSpeciesData:

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

La funzione getSpeciesData è una funzione di gestione asincrona passata al metodo get() del modulo express come secondo argomento. La funzione getSpeciesData() richiede due argomenti: un oggetto di richiesta e un oggetto di risposta. L’oggetto di richiesta contiene informazioni sul client, mentre l’oggetto di risposta contiene le informazioni che saranno inviate al client da Express.

Successivamente, aggiungi il codice evidenziato per chiamare fetchApiData() per recuperare i dati da un’API nella funzione di callback getSpeciesData():

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

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

Nella funzione, si estrae il valore catturato dal punto finale memorizzato nell’oggetto req.params, quindi lo si assegna alla variabile species. Nella riga successiva, si definisce la variabile results e si imposta su undefined.

Dopo di che, si invoca la funzione fetchApiData() con la variabile species come argomento. La chiamata della funzione fetchApiData() è preceduta dalla sintassi await perché restituisce una promessa. Quando la promessa si risolve, restituisce i dati, che vengono quindi assegnati alla variabile results.

Successivamente, aggiungi il codice evidenziato per gestire gli errori di runtime:

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

Si definisce il blocco try/catch per gestire gli errori di runtime. Nel blocco try, si chiama fetchApiData() per recuperare i dati da un’API.
Se si verifica un errore, il blocco catch registra l’errore e restituisce un codice di stato 404 con una risposta “Dati non disponibili”.

La maggior parte delle API restituisce un codice di stato 404 quando non hanno dati per una query specifica, il che attiva automaticamente l’esecuzione del blocco catch. Tuttavia, l’API FishWatch restituisce un codice di stato 200 con un array vuoto quando non ci sono dati per quella query specifica. Un codice di stato 200 significa che la richiesta è stata eseguita con successo, quindi il blocco catch() non viene mai attivato.

Per attivare il blocco catch(), è necessario verificare se l’array è vuoto e generare un errore quando la condizione dell’if si valuta come vera. Quando le condizioni dell’if si valutano come false, è possibile inviare una risposta al client contenente i dati.

Per farlo, aggiungi il codice evidenziato:

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

Una volta che i dati sono restituiti dall’API, l’istruzione if controlla se la variabile results è vuota. Se la condizione è soddisfatta, si utilizza l’istruzione throw per lanciare un errore personalizzato con il messaggio L'API ha restituito un array vuoto. Dopo che viene eseguito, l’esecuzione passa al blocco catch, che registra il messaggio di errore e restituisce una risposta 404.

Al contrario, se la variabile results contiene dati, la condizione dell’istruzione if non sarà soddisfatta. Di conseguenza, il programma salta il blocco if ed esegue il metodo send dell’oggetto di risposta, che invia una risposta al client.

Il metodo send accetta un oggetto che ha le seguenti proprietà:

  • fromCache: la proprietà accetta un valore che ti aiuta a sapere se i dati provengono dalla cache Redis o dall’API. Ora hai assegnato un valore false perché i dati provengono da un’API.

  • data: la proprietà è assegnata alla variabile results che contiene i dati restituiti dall’API.

A questo punto, il tuo codice completo avrà questo aspetto:

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

Ora che tutto è a posto, salva ed esci dal tuo file.

Avvia il server express:

  1. node server.js

L’API Fishwatch accetta molte specie, ma useremo solo la specie di pesce red-snapper come parametro di percorso sull’endpoint che testerai durante questo tutorial.

Ora avvia il tuo browser web preferito sul tuo computer locale. Naviga all’URL http://localhost:3000/fish/red-snapper.

Nota: Se stai seguendo il tutorial su un server remoto, puoi visualizzare l’app nel tuo browser utilizzando l’inoltro della porta.

Mantieni il server Node.js in esecuzione, apri un’altra finestra di terminale sul tuo computer locale e inserisci il seguente comando:

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

Dopo esserti connesso al server, vai a http://localhost:3000/fish/red-snapper sul browser web del tuo computer locale.

Una volta caricata la pagina, dovresti vedere fromCache impostato su false.

Ora, aggiorna l’URL altre tre volte e guarda il tuo terminale. Il terminale registrerà “Richiesta inviata all’API” tante volte quante avrai aggiornato il browser.

Se hai aggiornato l’URL tre volte dopo la visita iniziale, l’output sarà simile a questo:

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

Questo output mostra che viene inviata una richiesta di rete al server API ogni volta che aggiorni il browser. Se avessi un’applicazione con 1000 utenti che colpiscono lo stesso endpoint, ci sarebbero 1000 richieste di rete inviate all’API.

Quando implementi la memorizzazione nella cache, la richiesta all’API verrà effettuata solo una volta. Tutte le richieste successive otterranno i dati dalla cache, aumentando le prestazioni dell’applicazione.

Per ora, interrompi il tuo server Express con CTRL+C.

Ora che puoi richiedere dati da un’API e servirli agli utenti, memorizzerai i dati restituiti da un’API in Redis.

Passaggio 3 — Memorizzazione nella cache delle richieste API RESTful utilizzando Redis

In questa sezione, memorizzerai i dati dell’API in modo che solo la visita iniziale al punto finale della tua app richieda dati da un server API, e tutte le richieste successive recupereranno dati dalla cache Redis.

Apri il file server.js:

  1. nano server.js

Nel tuo file server.js, importa il modulo node-redis:

fish_wiki/server.js

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

Nello stesso file, connettiti a Redis utilizzando il modulo node-redis aggiungendo il codice evidenziato:

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

Prima, definisci la variabile redisClient con il valore impostato su undefined. Dopo di ciò, definisci una funzione asincrona anonima auto-invocata, che è una funzione che viene eseguita immediatamente dopo essere stata definita. Definisci una funzione asincrona anonima auto-invocata racchiudendo una definizione di funzione senza nome tra parentesi (async () => {...}). Per renderla auto-invocata, la segui immediatamente con un altro insieme di parentesi (), che alla fine appariranno come (async () => {...})().

All’interno della funzione, invochi il metodo createClient() del modulo redis che crea un oggetto redis. Poiché non hai fornito la porta da utilizzare da Redis quando hai invocato il metodo createClient(), Redis utilizzerà la porta 6379, la porta predefinita.

Chiami anche il metodo on() di Node.js che registra gli eventi sull’oggetto Redis. Il metodo on() accetta due argomenti: error e un callback. Il primo argomento error è un evento scatenato quando Redis incontra un errore. Il secondo argomento è un callback che viene eseguito quando l’evento error viene emesso. Il callback registra l’errore nella console.

Infine, chiami il metodo connect(), che avvia la connessione con Redis sulla porta predefinita 6379. Il metodo connect() restituisce una promessa, quindi lo prefissi con la sintassi await per risolverlo.

Ora che la tua applicazione è connessa a Redis, modificherai il callback getSpeciesData() per memorizzare i dati in Redis alla visita iniziale e recuperare i dati dalla cache per tutte le richieste successive.

Nel tuo file server.js, aggiungi e aggiorna il codice evidenziato:

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

Nella funzione getSpeciesData, si definisce la variabile isCached con il valore false. All’interno del blocco try, si chiama il metodo get() del modulo node-redis con species come argomento. Quando il metodo trova la chiave in Redis che corrisponde al valore della variabile species, restituisce i dati, che vengono quindi assegnati alla variabile cacheResults.

Successivamente, un’istruzione if verifica se la variabile cacheResults contiene dati. Se la condizione è verificata, la variabile isCached viene impostata su true. In seguito, si invoca il metodo parse() dell’oggetto JSON con cacheResults come argomento. Il metodo parse() converte i dati della stringa JSON in un oggetto JavaScript. Dopo che il JSON è stato analizzato, si invoca il metodo send(), che prende un oggetto con la proprietà fromCache impostata sulla variabile isCached. Il metodo invia la risposta al client.

Se il metodo get() del modulo node-redis non trova dati nella cache, la variabile cacheResults viene impostata su null. Di conseguenza, l’istruzione if viene valutata come falsa. Quando ciò accade, l’esecuzione passa al blocco else dove si chiama la funzione fetchApiData() per recuperare i dati dall’API. Tuttavia, una volta che i dati vengono restituiti dall’API, non vengono salvati in Redis.

Per memorizzare i dati nella cache Redis, è necessario utilizzare il metodo set() del modulo node-redis per salvarli. Per fare ciò, aggiungi la linea evidenziata:

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

All’interno del blocco else, una volta che i dati sono stati recuperati, chiami il metodo set() del modulo node-redis per salvare i dati in Redis sotto il nome chiave del valore nella variabile species.

Il metodo set() prende due argomenti, che sono coppie chiave-valore: species e JSON.stringify(results).

Il primo argomento, species, è la chiave sotto la quale i dati verranno salvati in Redis. Ricorda che species è impostato sul valore passato all’endpoint che hai definito. Ad esempio, quando visiti /fish/red-snapper, species è impostato su red-snapper, che sarà la chiave in Redis.

Il secondo argomento, JSON.stringify(results), è il valore per la chiave. Nel secondo argomento, invochi il metodo stringify() di JSON con la variabile results come argomento, che contiene i dati restituiti dall’API. Il metodo converte JSON in una stringa; ecco perché, quando hai recuperato i dati dalla cache usando il metodo get() del modulo node-redis in precedenza, hai invocato il metodo JSON.parse con la variabile cacheResults come argomento.

Il tuo file completo ora avrà questo aspetto:

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

Salva ed esci dal tuo file e esegui il server.js usando il comando node:

  1. node server.js

Una volta avviato il server, aggiorna http://localhost:3000/fish/red-snapper nel tuo browser.

Nota che fromCache è ancora impostato su false:

Ora aggiorna nuovamente la pagina per vedere che questa volta fromCache è impostato su true:

Aggiorna la pagina cinque volte e torna al terminale. Il tuo output sarà simile al seguente:

Output
App listening on port 3000 Request sent to the API

Adesso, Richiesta inviata all'API è stata registrata solo una volta dopo diversi aggiornamenti dell’URL, a differenza della sezione precedente dove il messaggio era stato registrato ad ogni aggiornamento. Questo output conferma che è stata inviata solo una richiesta al server e che successivamente i dati sono stati recuperati da Redis.

Per confermare ulteriormente che i dati sono memorizzati in Redis, interrompi il tuo server usando CTRL+C. Connettiti al client del server Redis con il seguente comando:

  1. redis-cli

Recupera i dati sotto la chiave red-snapper:

  1. get red-snapper

Il tuo output sarà simile al seguente (modificato per brevità):

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

L’output mostra la versione stringificata dei dati JSON che l’API restituisce quando visiti il punto finale /fish/red-snapper, confermando che i dati dell’API sono memorizzati nella cache di Redis.

Esci dal client del server Redis:

  1. exit

Ora che puoi memorizzare nella cache i dati di un’API, puoi anche impostare la validità della cache.

Passaggio 4 — Implementazione della Validità della Cache

Quando si memorizzano dati nella cache, è necessario sapere con quale frequenza i dati cambiano. Alcuni dati delle API cambiano in minuti; altri in ore, settimane, mesi o anni. Impostare una durata di cache adeguata garantisce che la tua applicazione fornisca dati aggiornati agli utenti.

In questo passaggio, imposterai la validità della cache per i dati delle API che devono essere memorizzati in Redis. Quando la cache scade, la tua applicazione invierà una richiesta all’API per recuperare dati recenti.

È necessario consultare la documentazione delle API per impostare il tempo di scadenza corretto per la cache. La maggior parte della documentazione menzionerà con quale frequenza vengono aggiornati i dati. Tuttavia, ci sono alcuni casi in cui la documentazione non fornisce le informazioni, quindi potresti dover fare delle supposizioni. Controllare la proprietà last_updated di vari endpoint delle API può mostrare con quale frequenza vengono aggiornati i dati.

Una volta scelta la durata della cache, è necessario convertirla in secondi. Per dimostrazione in questo tutorial, imposterai la durata della cache a 3 minuti o 180 secondi. Questa durata campione renderà più semplice testare la funzionalità della durata della cache.

Per implementare la durata di validità della cache, apri il file server.js:

  1. nano server.js

Aggiungi il codice evidenziato:

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

Nel metodo set() del modulo node-redis, passi un terzo argomento di un oggetto con le seguenti proprietà:

  • EX: accetta un valore con la durata della cache in secondi.
  • NX: quando impostato su true, garantisce che il metodo set() debba impostare solo una chiave che non esiste già in Redis.

Salva ed esci dal tuo file.

Torna al client del server Redis per testare la validità della cache:

  1. redis-cli

Elimina la chiave red-snapper in Redis:

  1. del red-snapper

Esci dal client Redis:

  1. exit

Ora, avvia il server di sviluppo con il comando node:

  1. node server.js

Torna al tuo browser e ricarica l’URL http://localhost:3000/fish/red-snapper. Per i prossimi tre minuti, se ricarichi l’URL, l’output nel terminale dovrebbe essere coerente con il seguente output:

Output
App listening on port 3000 Request sent to the API

Dopo che sono trascorsi tre minuti, ricarica l’URL nel tuo browser. Nel terminale, dovresti vedere che “Richiesta inviata all’API” è stato registrato due volte.

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

Questo output mostra che la cache è scaduta e è stata effettuata nuovamente una richiesta all’API.

Puoi interrompere il server Express.

Ora che puoi impostare la validità della cache, memorizzerai i dati usando il middleware.

Passaggio 5 — Memorizzazione nella Cache dei Dati nel Middleware

In questo passaggio, utilizzerai il middleware di Express per memorizzare nella cache i dati. Il middleware è una funzione che può accedere all’oggetto di richiesta, all’oggetto di risposta e a un callback che dovrebbe essere eseguito dopo la sua esecuzione. La funzione che viene eseguita dopo il middleware ha anche accesso all’oggetto di richiesta e di risposta. Quando si utilizza il middleware, è possibile modificare gli oggetti di richiesta e di risposta o restituire una risposta all’utente in precedenza.

Per utilizzare il middleware nella tua applicazione per la memorizzazione nella cache, modificherai la funzione gestore getSpeciesData() per recuperare i dati da un’API e memorizzarli in Redis. Sposterai tutto il codice che cerca i dati in Redis alla funzione middleware cacheData.

Quando visiti il punto di accesso /fish/:species, la funzione middleware verrà eseguita prima per cercare i dati nella cache; se trovati, restituirà una risposta e la funzione getSpeciesData non verrà eseguita. Tuttavia, se il middleware non trova i dati nella cache, chiamerà la funzione getSpeciesData per recuperare i dati dall’API e memorizzarli in Redis.

Innanzitutto, apri il tuo server.js:

  1. nano server.js

Successivamente, rimuovi il codice evidenziato:

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

Nella funzione getSpeciesData(), rimuovi tutto il codice che cerca i dati memorizzati in Redis. Rimuovi anche la variabile isCached poiché la funzione getSpeciesData() recupererà solo i dati dall’API e li memorizzerà in Redis.

Una volta rimosso il codice, impostare fromCache su false come evidenziato di seguito, in modo che la funzione getSpeciesData() appaia come segue:

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 funzione getSpeciesData() recupera i dati dall’API, li memorizza nella cache e restituisce una risposta all’utente.

Successivamente, aggiungi il seguente codice per definire la funzione middleware per la memorizzazione nella cache dei dati 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) {
...
}
...

La funzione middleware cacheData() prende tre argomenti: req, res e next. Nel blocco try, la funzione controlla se il valore della variabile species ha dati memorizzati in Redis sotto la sua chiave. Se i dati sono presenti in Redis, vengono restituiti e impostati nella variabile cacheResults.

Successivamente, l’istruzione if controlla se cacheResults contiene dati. I dati vengono salvati nella variabile results se l’istruzione è valutata come vera. Dopo di ciò, il middleware utilizza il metodo send() per restituire un oggetto con le proprietà fromCache impostata su true e data impostata sulla variabile results.

Tuttavia, se l’istruzione if viene valutata come falsa, l’esecuzione passa al blocco else. All’interno del blocco else, viene chiamato next(), che passa il controllo alla prossima funzione che dovrebbe eseguire dopo di essa.

Per fare in modo che la funzione middleware cacheData() passi il controllo alla funzione getSpeciesData() quando viene invocato next(), aggiorna di conseguenza il metodo get() del modulo express:

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

Il metodo get() ora prende cacheData come suo secondo argomento, che è il middleware che cerca i dati memorizzati nella cache Redis e restituisce una risposta quando trovati.

Ora, quando visiti il punto finale /fish/:species, cacheData() si esegue prima. Se i dati sono memorizzati nella cache, restituirà la risposta e qui termina il ciclo richiesta-risposta. Tuttavia, se non ci sono dati nella cache, verrà chiamato getSpeciesData() per recuperare i dati dall’API, memorizzarli nella cache e restituire una risposta.

Il file completo avrà questo aspetto:

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

Salva ed esci dal tuo file.

Per testare correttamente la memorizzazione nella cache, puoi eliminare la chiave red-snapper in Redis. Per farlo, accedi al client Redis:

  1. redis-cli

Rimuovi la chiave red-snapper:

  1. del red-snapper

Esci dal client Redis:

  1. exit

Ora, esegui il file server.js:

  1. node server.js

Una volta avviato il server, torna al browser e visita nuovamente http://localhost:3000/fish/red-snapper. Aggiorna più volte.

Il terminale registrerà il messaggio che è stata inviata una richiesta all’API. Il middleware cacheData() servirà tutte le richieste per i prossimi tre minuti. Il tuo output sarà simile a questo se aggiorni casualmente l’URL in uno spazio di tempo di quattro minuti:

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

Il comportamento è coerente con il funzionamento dell’applicazione nella sezione precedente.

Ora puoi memorizzare i dati nella cache in Redis utilizzando il middleware.

Conclusione

In questo articolo, hai costruito un’applicazione che recupera dati da un’API e restituisce i dati come risposta al client. Successivamente, hai modificato l’app per memorizzare nella cache la risposta dell’API in Redis alla prima visita e servire i dati dalla cache per tutte le richieste successive. Hai poi modificato la durata della cache in modo che scada dopo un certo periodo di tempo trascorso, e quindi hai utilizzato il middleware per gestire il recupero dei dati dalla cache.

Come prossimo passo, puoi esplorare la documentazione di Node Redis per apprendere di più sulle funzionalità disponibili nel modulo node-redis. Puoi anche leggere la documentazione di Axios e Express per una visione più approfondita degli argomenti trattati in questo tutorial.

Per continuare a sviluppare le tue competenze in Node.js, consulta la serie Come Codificare in Node.js.

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