Como implementar cache em Node.js usando Redis

O autor selecionou /dev/color para receber uma doação como parte do programa Write for DOnations.

Introdução

A maioria das aplicações depende de dados, quer venham de um banco de dados ou de uma API. Obter dados de uma API envia uma solicitação de rede para o servidor da API e retorna os dados como resposta. Essas idas e vindas levam tempo e podem aumentar o tempo de resposta da sua aplicação para os usuários. Além disso, a maioria das APIs limita o número de solicitações que podem atender a uma aplicação dentro de um período de tempo específico, um processo conhecido como limitação de taxa.

Para contornar esses problemas, você pode armazenar em cache seus dados para que a aplicação faça uma única solicitação a uma API, e todas as solicitações de dados subsequentes irão recuperar os dados do cache. O Redis, um banco de dados em memória que armazena dados na memória do servidor, é uma ferramenta popular para armazenar em cache de dados. Você pode se conectar ao Redis no Node.js usando o módulo node-redis, que oferece métodos para recuperar e armazenar dados no Redis.

Neste tutorial, você irá construir uma aplicação Express que recupera dados de uma API RESTful usando o módulo axios. Em seguida, você irá modificar o aplicativo para armazenar os dados obtidos da API no Redis usando o módulo node-redis. Depois disso, você irá implementar o período de validade do cache para que o cache possa expirar após um certo período de tempo ter passado. Finalmente, você irá utilizar o middleware Express para armazenar em cache os dados.

Pré-requisitos

Para seguir o tutorial, você vai precisar de:

Passo 1 — Configurando o Projeto

Neste passo, você irá instalar as dependências necessárias para este projeto e iniciar um servidor Express. Neste tutorial, você criará uma wiki contendo informações sobre diferentes tipos de peixes. Chamaremos o projeto de fish_wiki.

Primeiro, crie o diretório para o projeto usando o comando mkdir:

  1. mkdir fish_wiki

Mova-se para o diretório:

  1. cd fish_wiki

Inicialize o arquivo package.json usando o comando npm:

  1. npm init -y

A opção -y aceita todos os padrões automaticamente.

Ao executar o comando npm init, ele criará o arquivo package.json em seu diretório com o seguinte conteúdo:

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

Em seguida, você instalará os seguintes pacotes:

  • express: um framework de servidor web para Node.js.
  • axios: um cliente HTTP Node.js, útil para fazer chamadas de API.
  • node-redis: um cliente Redis que permite armazenar e acessar dados no Redis.

Para instalar os três pacotes juntos, insira o seguinte comando:

  1. npm install express axios redis

Depois de instalar os pacotes, você criará um servidor Express básico.

Usando o nano ou o editor de texto de sua escolha, crie e abra o arquivo server.js:

  1. nano server.js

No seu arquivo server.js, insira o seguinte código para criar um servidor 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}`);
});

Primeiro, importe o express para o arquivo. Na segunda linha, defina a variável app como uma instância de express, que lhe dá acesso a métodos como get, post, listen, e muitos mais. Este tutorial se concentrará nos métodos get e listen.

Na linha seguinte, defina e atribua a variável port ao número de porta que você deseja que o servidor escute. Se nenhum número de porta estiver disponível em um arquivo de variáveis ambientais, a porta 3000 será usada como padrão.

Finalmente, usando a variável app, você invoca o método listen() do módulo express para iniciar o servidor na porta 3000.

Salve e feche o arquivo.

Execute o arquivo server.js usando o comando node para iniciar o servidor:

  1. node server.js

O console irá registrar uma mensagem semelhante à seguinte:

Output
App listening on port 3000

A saída confirma que o servidor está em execução e pronto para atender a quaisquer solicitações na porta 3000. Como o Node.js não recarrega automaticamente o servidor quando os arquivos são alterados, agora você irá parar o servidor usando CTRL+C para que possa atualizar server.js no próximo passo.

Depois de ter instalado as dependências e criado um servidor Express, você recuperará dados de uma API RESTful.

Passo 2 — Recuperando Dados de uma API RESTful Sem Caching

Neste passo, você irá construir sobre o servidor Express do passo anterior para recuperar dados de uma API RESTful sem implementar cache, demonstrando o que acontece quando os dados não são armazenados em um cache.

Para começar, abra o arquivo server.js em seu editor de texto:

  1. nano server.js

Em seguida, você irá recuperar dados da API FishWatch. A API FishWatch retorna informações sobre espécies de peixes.

No seu arquivo server.js, defina uma função que solicite dados da API com o seguinte código destacado:

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

Na segunda linha, você importa o módulo axios. Em seguida, você define uma função assíncrona fetchApiData(), que recebe species como parâmetro. Para tornar a função assíncrona, você a prefixa com a palavra-chave async.

Dentro da função, você chama o método get() do módulo axios com o endpoint da API de onde deseja que o método recupere os dados, que é a API FishWatch neste exemplo. Como o método get() implementa uma promessa, você a prefixa com a palavra-chave await para resolver a promessa. Uma vez que a promessa é resolvida e os dados são retornados da API, você chama o método console.log(). O método console.log() irá registrar uma mensagem dizendo que uma solicitação foi enviada para a API. Por fim, você retorna os dados da API.

Em seguida, você definirá uma rota Express que aceita solicitações GET. No seu arquivo server.js, defina a rota com o seguinte código:

fish_wiki/server.js
...

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

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

No bloco de código anterior, você invoca o método get() do módulo express, que apenas escuta as solicitações GET. O método recebe dois argumentos:

  • /peixe/:especie: o endpoint que o Express estará ouvindo. O endpoint recebe um parâmetro de rota :especie que captura qualquer coisa inserida nessa posição na URL.
  • getSpeciesData() (ainda não definido): uma função de retorno de chamada que será chamada quando a URL corresponder ao endpoint especificado no primeiro argumento.

Agora que a rota está definida, especifique a função de retorno de chamada getSpeciesData:

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

A função getSpeciesData é uma função de manipulador assíncrona passada para o método get() do módulo express como segundo argumento. A função getSpeciesData() recebe dois argumentos: um objeto de requisição e um objeto de resposta. O objeto de requisição contém informações sobre o cliente, enquanto o objeto de resposta contém as informações que serão enviadas para o cliente a partir do Express.

Em seguida, adicione o código destacado para chamar fetchApiData() para recuperar dados de uma API na função de retorno de chamada getSpeciesData():

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

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

Na função, você extrai o valor capturado do endpoint armazenado no objeto req.params, em seguida, atribui-o à variável species. Na linha seguinte, você define a variável results e a define como undefined.

Depois disso, você invoca a função fetchApiData() com a variável species como argumento. A chamada da função fetchApiData() é prefixada com a sintaxe await porque ela retorna uma promise. Quando a promise é resolvida, ela retorna os dados, que são então atribuídos à variável results.

Em seguida, adicione o código destacado para lidar com erros em tempo de execução:

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

Você define o bloco try/catch para lidar com erros em tempo de execução. No bloco try, você chama fetchApiData() para recuperar dados de uma API. Se ocorrer um erro, o bloco catch registra o erro e retorna um código de status 404 com uma resposta “Dados indisponíveis”.

A maioria das APIs retorna um código de status 404 quando não há dados para uma consulta específica, o que aciona automaticamente a execução do bloco catch. No entanto, a API FishWatch retorna um código de status 200 com uma matriz vazia quando não há dados para aquela consulta específica. Um código de status 200 significa que a solicitação foi bem-sucedida, então o bloco catch() nunca é acionado.

Para acionar o bloco catch(), você precisa verificar se a matriz está vazia e lançar um erro quando a condição do if avalia como verdadeira. Quando as condições do if avaliam como falsas, você pode enviar uma resposta ao cliente contendo os dados.

Para fazer isso, adicione o código destacado:

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

Uma vez que os dados são retornados da API, a instrução if verifica se a variável results está vazia. Se a condição for atendida, você usa a instrução throw para lançar um erro personalizado com a mensagem A API retornou um array vazio. Após sua execução, a execução passa para o bloco catch, que registra a mensagem de erro e retorna uma resposta 404.

Por outro lado, se a variável results contiver dados, a condição da instrução if não será atendida. Como resultado, o programa irá pular o bloco if e executar o método send do objeto de resposta, que envia uma resposta para o cliente.

O método send recebe um objeto que possui as seguintes propriedades:

  • fromCache: a propriedade aceita um valor que ajuda você a saber se os dados estão vindo do cache do Redis ou da API. Você agora atribuiu um valor false porque os dados vêm de uma API.

  • data: a propriedade é atribuída à variável results que contém os dados retornados da API.

Neste ponto, seu código completo ficará assim:

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

Agora que tudo está no lugar, salve e saia do seu arquivo.

Inicie o servidor express:

  1. node server.js

A API Fishwatch aceita muitas espécies, mas usaremos apenas a espécie de peixe red-snapper como um parâmetro de rota no endpoint que você estará testando ao longo deste tutorial.

Agora, abra seu navegador da web favorito no seu computador local. Acesse a URL http://localhost:3000/fish/red-snapper.

Nota: Se você estiver seguindo o tutorial em um servidor remoto, pode visualizar o aplicativo no seu navegador usando encaminhamento de porta.

Com o servidor Node.js ainda em execução, abra outro terminal no seu computador local e digite o seguinte comando:

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

Ao se conectar ao servidor, acesse http://localhost:3000/fish/red-snapper no navegador da web do seu computador local.

Ao carregar a página, você deverá ver fromCache definido como false.

Agora, atualize a URL mais três vezes e observe o seu terminal. O terminal registrará “Request sent to the API” tantas vezes quanto você atualizou o navegador.

Se você atualizou a URL três vezes após a visita inicial, sua saída será assim:

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

Esta saída mostra que uma solicitação de rede é enviada ao servidor da API cada vez que você atualiza o navegador. Se você tivesse um aplicativo com 1000 usuários acessando o mesmo endpoint, seriam 1000 solicitações de rede enviadas para a API.

Ao implementar o cache, a solicitação à API será feita apenas uma vez. Todas as solicitações subsequentes obterão dados do cache, aumentando o desempenho do seu aplicativo.

Por enquanto, pare o servidor Express com CTRL+C.

Agora que você pode solicitar dados de uma API e servi-los aos usuários, você irá armazenar em cache os dados retornados de uma API no Redis.

Passo 3 — Armazenando em Cache Requisições de API RESTful Usando o Redis

Nesta seção, você irá armazenar em cache os dados da API de forma que apenas a visita inicial ao endpoint do seu aplicativo solicitará dados a partir de um servidor de API, e todas as solicitações subsequentes buscarão dados do cache Redis.

Abra o arquivo server.js:

  1. nano server.js

No seu arquivo server.js, importe o módulo node-redis:

fish_wiki/server.js

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

No mesmo arquivo, conecte-se ao Redis usando o módulo node-redis adicionando o código destacado:

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

Primeiro, você define a variável redisClient com o valor indefinido. Depois disso, você define uma função assíncrona anônima auto-invocada, que é uma função que é executada imediatamente após ser definida. Você define uma função assíncrona anônima auto-invocada envolvendo uma definição de função sem nome em parênteses (async () => {...}). Para torná-la auto-invocada, você imediatamente a segue com outro conjunto de parênteses (), que acaba parecendo (async () => {...})().

Dentro da função, você invoca o método createClient() do módulo redis que cria um objeto redis. Como você não forneceu a porta para o Redis usar ao invocar o método createClient(), o Redis usará a porta 6379, a porta padrão.

Você também chama o método on() do Node.js que registra eventos no objeto Redis. O método on() recebe dois argumentos: error e um callback. O primeiro argumento error é um evento disparado quando o Redis encontra um erro. O segundo argumento é um callback que é executado quando o evento error é emitido. O callback registra o erro no console.

Por fim, você chama o método connect(), que inicia a conexão com o Redis na porta padrão 6379. O método connect() retorna uma promessa, então você o prefixa com a sintaxe await para resolvê-lo.

Agora que sua aplicação está conectada ao Redis, você irá modificar o callback getSpeciesData() para armazenar dados no Redis na visita inicial e recuperar os dados do cache para todas as solicitações subsequentes.

No seu arquivo server.js, adicione e atualize o código destacado:

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

Na função getSpeciesData, você define a variável isCached com o valor false. Dentro do bloco try, você chama o método get() do módulo node-redis com species como argumento. Quando o método encontra a chave no Redis que corresponde ao valor da variável species, ele retorna os dados, que então são atribuídos à variável cacheResults.

Em seguida, uma instrução if verifica se a variável cacheResults possui dados. Se a condição for atendida, a variável isCache é atribuída como true. Seguindo isso, você invoca o método parse() do objeto JSON com cacheResults como argumento. O método parse() converte dados de string JSON em um objeto JavaScript. Após o JSON ser analisado, você invoca o método send(), que recebe um objeto com a propriedade fromCache definida como a variável isCached. O método envia a resposta para o cliente.

Se o método get() do módulo node-redis não encontrar dados no cache, a variável cacheResults é definida como null. Como resultado, a instrução if é avaliada como falsa. Quando isso acontece, a execução pula para o bloco else, onde você chama a função fetchApiData() para buscar dados da API. No entanto, uma vez que os dados são retornados pela API, eles não são salvos no Redis.

Para armazenar os dados no cache do Redis, você precisa usar o método set() do módulo node-redis para salvá-los. Para fazer isso, adicione a linha destacada:

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

Dentro do bloco else, uma vez que os dados tenham sido buscados, você chama o método set() do módulo node-redis para salvar os dados no Redis sob o nome da chave do valor na variável species.

O método set() recebe dois argumentos, que são pares chave-valor: species e JSON.stringify(results).

O primeiro argumento, species, é a chave sob a qual os dados serão salvos no Redis. Lembre-se de que species é definido como o valor passado para o endpoint que você definiu. Por exemplo, ao visitar /fish/red-snapper, species é definido como red-snapper, que será a chave no Redis.

O segundo argumento, JSON.stringify(results), é o valor para a chave. No segundo argumento, você invoca o método stringify() do JSON com a variável results como argumento, que contém os dados retornados da API. O método converte JSON em uma string; é por isso que, ao recuperar dados do cache usando o método get() do módulo node-redis anteriormente, você invocou o método JSON.parse com a variável cacheResults como argumento.

Seu arquivo completo agora parecerá o seguinte:

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

Salve e saia do seu arquivo e execute o server.js usando o comando node:

  1. node server.js

Assim que o servidor iniciar, atualize http://localhost:3000/fish/red-snapper no seu navegador.

Observe que fromCache ainda está definido como false:

Agora atualize a página novamente para ver que desta vez fromCache está definido como true:

Atualize a página cinco vezes e volte para o terminal. Sua saída será semelhante ao seguinte:

Output
App listening on port 3000 Request sent to the API

Agora, Solicitação enviada para a API foi registrada apenas uma vez após várias atualizações de URL, contrastando com a última seção onde a mensagem foi registrada para cada atualização. Esta saída confirma que apenas uma solicitação foi enviada para o servidor e que, posteriormente, os dados são buscados do Redis.

Para confirmar ainda mais que os dados estão armazenados no Redis, pare o seu servidor usando CTRL+C. Conecte-se ao cliente do servidor Redis com o seguinte comando:

  1. redis-cli

Recupere os dados sob a chave red-snapper:

  1. get red-snapper

Sua saída será semelhante ao seguinte (editado para brevidade):

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

A saída mostra a versão em string dos dados JSON que a API retorna quando você visita o endpoint /fish/red-snapper, confirmando que os dados da API estão armazenados no cache do Redis.

Saia do cliente do servidor Redis:

  1. exit

Agora que você pode armazenar em cache dados de uma API, também pode definir a validade do cache.

Passo 4 — Implementando a Validade do Cache

Ao armazenar dados em cache, você precisa saber com que frequência os dados mudam. Alguns dados de API mudam em minutos; outros em horas, semanas, meses ou anos. Definir uma duração de cache adequada garante que sua aplicação sirva dados atualizados aos usuários.

Neste passo, você definirá a validade do cache para os dados da API que precisam ser armazenados no Redis. Quando o cache expirar, sua aplicação enviará uma solicitação à API para recuperar dados recentes.

Você precisa consultar a documentação da sua API para definir o tempo de expiração correto para o cache. A maioria das documentações mencionará com que frequência os dados são atualizados. No entanto, há casos em que a documentação não fornece as informações, então você pode ter que adivinhar. Verificar a propriedade last_updated de vários pontos de extremidade da API pode mostrar com que frequência os dados são atualizados.

Depois de escolher a duração do cache, você precisa convertê-lo em segundos. Para demonstração neste tutorial, você definirá a duração do cache para 3 minutos ou 180 segundos. Essa duração de amostra facilitará o teste da funcionalidade de duração do cache.

Para implementar a duração da validade do cache, abra o arquivo server.js:

  1. nano server.js

Adicione o código destacado:

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

No método set() do módulo node-redis, você passa um terceiro argumento de um objeto com as seguintes propriedades:

  • EX: aceita um valor com a duração do cache em segundos.
  • NX: quando definido como true, garante que o método set() só deve definir uma chave que ainda não exista no Redis.

Salve e saia do seu arquivo.

Volte para o cliente do servidor Redis para testar a validade do cache:

  1. redis-cli

Exclua a chave red-snapper no Redis:

  1. del red-snapper

Saia do cliente Redis:

  1. exit

Agora, inicie o servidor de desenvolvimento com o comando node:

  1. node server.js

Volte para o navegador e atualize a URL http://localhost:3000/fish/red-snapper. Pelos próximos três minutos, se você atualizar a URL, a saída no terminal deve ser consistente com a seguinte saída:

Output
App listening on port 3000 Request sent to the API

Depois que três minutos se passarem, atualize a URL no seu navegador. No terminal, você deve ver que “Requisição enviada para a API” foi registrado duas vezes.

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

Esta saída mostra que o cache expirou e uma requisição para a API foi feita novamente.

Você pode parar o servidor Express.

Agora que você pode definir a validade do cache, você irá cacheiar dados usando middleware em seguida.

Passo 5 — Cacheando Dados em Middleware

Neste passo, você usará o middleware Express para cacheiar dados. Middleware é uma função que pode acessar o objeto de requisição, objeto de resposta e um callback que deve ser executado depois que ele é executado. A função que é executada após o middleware também tem acesso ao objeto de requisição e resposta. Ao usar middleware, você pode modificar objetos de requisição e resposta ou retornar uma resposta para o usuário mais cedo.

Para usar middleware em sua aplicação para armazenamento em cache, você modificará a função getSpeciesData() para buscar dados de uma API e armazená-los no Redis. Você moverá todo o código que procura dados no Redis para a função middleware cacheData.

Ao visitar o endpoint /fish/:species, a função middleware será executada primeiro para procurar dados no cache; se encontrados, ela retornará uma resposta e a função getSpeciesData não será executada. No entanto, se o middleware não encontrar os dados no cache, ele chamará a função getSpeciesData para buscar os dados na API e armazená-los no Redis.

Primeiro, abra seu server.js:

  1. nano server.js

Em seguida, remova o código destacado:

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

Na função getSpeciesData(), você remove todo o código que procura dados armazenados no Redis. Você também remove a variável isCached, já que a função getSpeciesData() só buscará dados na API e os armazenará no Redis.

Depois que o código for removido, defina fromCache como false conforme destacado abaixo, para que a função getSpeciesData() fique assim:

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

A função getSpeciesData() obtém os dados da API, os armazena no cache e retorna uma resposta ao usuário.

Em seguida, adicione o seguinte código para definir a função middleware para armazenar dados em cache no 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) {
...
}
...

A função de middleware cacheData() recebe três argumentos: req, res e next. No bloco try, a função verifica se o valor na variável species possui dados armazenados no Redis sob sua chave. Se os dados estiverem no Redis, eles são retornados e definidos na variável cacheResults.

Em seguida, o comando if verifica se cacheResults possui dados. Os dados são salvos na variável results se o resultado for verdadeiro. Depois disso, o middleware utiliza o método send() para retornar um objeto com as propriedades fromCache definida como true e data definida como a variável results.

No entanto, se o comando if for avaliado como falso, a execução passa para o bloco else. Dentro do bloco else, você chama next(), que passa o controle para a próxima função que deve ser executada após ela.

Para fazer com que o middleware cacheData() passe o controle para a função getSpeciesData() quando next() é invocado, atualize o método get() do módulo express da seguinte forma:

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

O método get() agora recebe cacheData como seu segundo argumento, que é o middleware que busca dados em cache no Redis e retorna uma resposta quando encontrados.

Agora, quando você visitar o endpoint /fish/:species, cacheData() será executado primeiro. Se os dados estiverem em cache, ele retornará a resposta, e o ciclo de solicitação-resposta termina aqui. No entanto, se nenhum dado for encontrado no cache, o getSpeciesData() será chamado para recuperar dados da API, armazená-los no cache e retornar uma resposta.

O arquivo completo agora ficará assim:

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

Salve e saia do seu arquivo.

Para testar o cache adequadamente, você pode excluir a chave red-snapper no Redis. Para fazer isso, acesse o cliente Redis:

  1. redis-cli

Remova a chave red-snapper:

  1. del red-snapper

Saia do cliente Redis:

  1. exit

Agora, execute o arquivo server.js:

  1. node server.js

Assim que o servidor iniciar, volte para o navegador e visite novamente o http://localhost:3000/fish/red-snapper. Atualize várias vezes.

O terminal registrará a mensagem de que uma solicitação foi enviada para a API. O middleware cacheData() atenderá todas as solicitações pelos próximos três minutos. Sua saída será semelhante a esta se você atualizar aleatoriamente a URL em um intervalo de quatro minutos:

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

O comportamento é consistente com o funcionamento da aplicação na seção anterior.

Agora você pode armazenar em cache dados no Redis usando middleware.

Conclusão

Neste artigo, você construiu uma aplicação que busca dados de uma API e retorna esses dados como resposta ao cliente. Em seguida, você modificou o aplicativo para armazenar em cache a resposta da API no Redis na visita inicial e servir os dados do cache para todas as solicitações subsequentes. Você modificou a duração do cache para expirar após um certo período de tempo e depois usou middleware para lidar com a recuperação dos dados do cache.

Como próximo passo, você pode explorar a documentação do Node Redis para aprender mais sobre os recursos disponíveis no módulo node-redis. Você também pode ler a documentação do Axios e do Express para uma visão mais aprofundada dos tópicos abordados neste tutorial.

Para continuar a desenvolver suas habilidades em Node.js, consulte a série Como Codificar em Node.js.

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