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:
-
Configuração do ambiente Node.js em seu servidor. Se estiver no Ubuntu 22.04, instale a versão mais recente do Node.js e do npm seguindo a opção 3 em Como Instalar o Node.js no Ubuntu 22.04. Para outros sistemas operacionais, consulte a série Como Instalar o Node.js e Criar um Ambiente de Desenvolvimento Local.
-
O Redis está instalado no seu servidor. Se estiver usando o Ubuntu 22.04, siga os passos 1 e 2 de Como Instalar e Proteger o Redis no Ubuntu 22.04. Se estiver trabalhando em outro sistema operacional, consulte Como Instalar e Proteger o Redis.
-
Conhecimento de programação assíncrona. Consulte Entendendo o Event Loop, Callbacks, Promises e Async/Await em JavaScript.
-
Conhecimento básico utilizando o framework web Express. Veja Como começar com Node.js e Express.
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
:
Mova-se para o diretório:
Inicialize o arquivo package.json
usando o comando npm
:
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:
OutputWrote 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:
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
:
No seu arquivo server.js
, insira o seguinte código para criar um servidor Express:
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:
O console irá registrar uma mensagem semelhante à seguinte:
OutputApp 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:
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:
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:
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
:
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()
:
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:
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:
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 valorfalse
porque os dados vêm de uma API. -
data
: a propriedade é atribuída à variávelresults
que contém os dados retornados da API.
Neste ponto, seu código completo ficará assim:
Agora que tudo está no lugar, salve e saia do seu arquivo.
Inicie o servidor express:
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:
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
:
No seu arquivo server.js
, importe o módulo node-redis
:
No mesmo arquivo, conecte-se ao Redis usando o módulo node-redis
adicionando o código destacado:
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:
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:
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:
Salve e saia do seu arquivo e execute o server.js
usando o comando node
:
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:
OutputApp 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:
Recupere os dados sob a chave 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:
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
:
Adicione o código destacado:
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 comotrue
, garante que o métodoset()
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:
Exclua a chave red-snapper
no Redis:
Saia do cliente Redis:
Agora, inicie o servidor de desenvolvimento com o comando node
:
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:
OutputApp 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.
OutputApp 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
:
Em seguida, remova o código destacado:
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:
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:
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:
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:
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:
Remova a chave red-snapper
:
Saia do cliente Redis:
Agora, execute o arquivo 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:
OutputApp 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