Как реализовать кэширование в Node.js с использованием Redis

Автор выбрал /dev/color для получения пожертвования в рамках программы Write for DOnations.

Введение

Большинство приложений зависят от данных, будь то из базы данных или API. Получение данных из API отправляет сетевой запрос на сервер API и возвращает данные в качестве ответа. Эти круговые поездки занимают время и могут увеличить время ответа вашего приложения для пользователей. Кроме того, большинство API ограничивают количество запросов, которые они могут обслуживать приложению в определенный промежуток времени, процесс, известный как ограничение скорости.

Чтобы избежать этих проблем, вы можете кэшировать свои данные, чтобы приложение делало один запрос к API, и все последующие запросы данных будут извлекать данные из кэша. Redis, база данных в памяти, которая хранит данные в памяти сервера, является популярным инструментом для кэширования данных. Вы можете подключиться к Redis в Node.js, используя модуль node-redis, который предоставляет вам методы для извлечения и сохранения данных в Redis.

В этом руководстве вы создадите приложение Express, которое извлекает данные из RESTful API, используя модуль axios. Затем вы измените приложение, чтобы сохранить данные, полученные из API, в Redis, используя модуль node-redis. После этого вы реализуете период действия кэша, чтобы кэш мог истечь после прохождения определенного времени. Наконец, вы будете использовать промежуточное ПО Express для кэширования данных.

Предварительные требования

Для выполнения этого руководства вам понадобится:

Шаг 1 — Настройка проекта

На этом этапе вы установите необходимые зависимости для этого проекта и запустите сервер Express. В этом учебнике вы создадите вики, содержащую информацию о различных видах рыб. Мы назовем проект fish_wiki.

Сначала создайте каталог для проекта, используя команду mkdir:

  1. mkdir fish_wiki

Перейдите в каталог:

  1. cd fish_wiki

Инициализируйте файл package.json, используя команду npm:

  1. npm init -y

Опция -y автоматически принимает все значения по умолчанию.

При выполнении команды npm init будет создан файл package.json в вашем каталоге со следующим содержимым:

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

Затем установите следующие пакеты:

  • express: веб-серверный фреймворк для Node.js.
  • axios: клиент HTTP для Node.js, который полезен для выполнения вызовов API.
  • node-redis: клиент Redis, который позволяет хранить и получать данные в Redis.

Чтобы установить все три пакета вместе, выполните следующую команду:

  1. npm install express axios redis

После установки пакетов вы создадите базовый сервер Express.

С помощью nano или текстового редактора на ваш выбор, создайте и откройте файл server.js:

  1. nano server.js

В файле server.js введите следующий код для создания сервера 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}`);
});

Сначала импортируйте express в файл. Во второй строке установите переменную app как экземпляр express, что дает вам доступ к методам, таким как get, post, listen и многим другим. В этом учебнике будет рассмотрено использование методов get и listen.

В следующей строке определите и назначьте переменной port номер порта, на котором вы хотите, чтобы сервер слушал. Если номер порта не указан в файле переменных среды, по умолчанию будет использоваться порт 3000.

Наконец, с использованием переменной app вы вызываете метод listen() модуля express, чтобы запустить сервер на порту 3000.

Сохраните и закройте файл.

Запустите файл server.js с помощью команды node, чтобы запустить сервер:

  1. node server.js

Консоль выведет сообщение, аналогичное следующему:

Output
App listening on port 3000

Вывод подтверждает, что сервер работает и готов обслуживать любые запросы на порту 3000. Поскольку Node.js не автоматически перезагружает сервер при изменении файлов, вы теперь остановите сервер, используя CTRL+C, чтобы можно было обновить server.js на следующем этапе.

После установки зависимостей и создания сервера Express вы будете извлекать данные из RESTful API.

Шаг 2 — Извлечение данных из RESTful API без кэширования

На этом этапе вы продолжите работу с сервером Express из предыдущего шага, чтобы извлечь данные из RESTful API без реализации кэширования, демонстрируя, что происходит, когда данные не хранятся в кэше.

Для начала откройте файл server.js в вашем текстовом редакторе:

  1. nano server.js

Затем вы извлечете данные из API FishWatch. API FishWatch возвращает информацию о видов рыб.

В вашем файле server.js определите функцию, которая запрашивает данные API с использованием следующего выделенного кода:

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

На второй строке вы импортируете модуль axios. Затем вы определяете асинхронную функцию fetchApiData(), которая принимает species в качестве параметра. Чтобы сделать функцию асинхронной, вы добавляете ключевое слово async.

Внутри функции вы вызываете метод get() модуля axios с конечной точкой API, откуда вы хотите получить данные, которые в этом примере являются API FishWatch. Поскольку метод get() реализует промис, вы добавляете перед ним ключевое слово await для разрешения промиса. Как только промис разрешается и данные возвращаются из API, вы вызываете метод console.log(). Метод console.log() будет записывать сообщение о том, что запрос был отправлен на API. Наконец, вы возвращаете данные из API.

Затем вы определите маршрут Express, который принимает запросы GET. В вашем файле server.js определите маршрут следующим кодом:

fish_wiki/server.js
...

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

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

В предыдущем блоке кода вы вызываете метод get() модуля express, который прослушивает только запросы GET. Метод принимает два аргумента:

  • /fish/:вид: конечная точка, на которой будет слушать Express. Конечная точка принимает параметр маршрута :вид, который захватывает все, что введено на этой позиции в URL.
  • getSpeciesData() (пока не определена): функция обратного вызова, которая будет вызываться, когда URL совпадает с указанной конечной точкой в первом аргументе.

Теперь, когда маршрут определен, укажите функцию обратного вызова getSpeciesData:

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

Функция getSpeciesData – это асинхронная обработчиковая функция, передаваемая методу get() модуля express в качестве второго аргумента. Функция getSpeciesData() принимает два аргумента: объект запроса и объект ответа. Объект запроса содержит информацию о клиенте, а объект ответа содержит информацию, которая будет отправлена клиенту из Express.

Затем добавьте выделенный код для вызова fetchApiData() для получения данных из API в функции обратного вызова getSpeciesData():

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

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

В этой функции вы извлекаете значение, захваченное из конечной точки, хранящееся в объекте req.params, затем присваиваете его переменной species. В следующей строке вы определяете переменную results и устанавливаете ее в undefined.

После этого вызывается функция fetchApiData() с переменной species в качестве аргумента. Вызов функции fetchApiData() предварен синтаксисом await, потому что она возвращает обещание. Когда обещание разрешается, оно возвращает данные, которые затем присваиваются переменной results.

Затем добавьте выделенный код для обработки ошибок времени выполнения:

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

Вы определяете блок try/catch для обработки ошибок времени выполнения. В блоке try вы вызываете fetchApiData() для извлечения данных из API.
Если происходит ошибка, блок catch регистрирует ошибку и возвращает код состояния 404 с ответом “Данные недоступны”.

Большинство API возвращают код состояния 404, когда у них нет данных для определенного запроса, что автоматически вызывает выполнение блока catch. Однако API FishWatch возвращает код состояния 200 с пустым массивом, когда нет данных для этого конкретного запроса. Код состояния 200 означает успешный запрос, поэтому блок catch() никогда не срабатывает.

Чтобы сработал блок catch(), необходимо проверить, является ли массив пустым, и вызвать ошибку, когда условие if вычисляется как true. Когда условия if вычисляются как false, можно отправить ответ клиенту, содержащий данные.

Для этого добавьте выделенный код:

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

После того, как данные возвращены из API, оператор if проверяет, пустая ли переменная results. Если условие выполняется, используется оператор throw для генерации пользовательской ошибки с сообщением API вернул пустой массив. После выполнения этого оператора выполнение переходит к блоку catch, который регистрирует сообщение об ошибке и возвращает ответ 404.

Напротив, если переменная results содержит данные, условие оператора if не будет выполнено. В результате программа пропустит блок if и выполнит метод send объекта ответа, который отправляет ответ клиенту.

Метод send принимает объект с следующими свойствами:

  • fromCache: свойство принимает значение, которое помогает определить, данные из Redis кеша или из API. Вы присвоили значение false, потому что данные поступают из API.

  • data: свойству присваивается переменная results, содержащая данные, возвращенные из API.

На данном этапе ваш полный код будет выглядеть следующим образом:

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

Теперь, когда всё на своем месте, сохраните и закройте ваш файл.

Запустите сервер Express:

  1. node server.js

API Fishwatch принимает множество видов рыб, но мы будем использовать только вид рыбы red-snapper в качестве параметра маршрута на конечной точке, которую вы будете тестировать в течение этого руководства.

Теперь запустите свой любимый веб-браузер на локальном компьютере. Перейдите по URL http://localhost:3000/fish/red-snapper.

Примечание: Если вы следуете за руководством на удаленном сервере, вы можете просмотреть приложение в браузере, используя перенаправление портов.

Сервер Node.js все еще запущен, откройте еще один терминал на локальном компьютере, а затем введите следующую команду:

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

Подключившись к серверу, перейдите по адресу http://localhost:3000/fish/red-snapper в веб-браузере на вашем локальном компьютере.

Когда страница загрузится, вы увидите, что fromCache установлен в false.

Теперь обновите URL еще три раза и посмотрите на ваш терминал. Терминал будет регистрировать “Запрос отправлен на сервер API” столько раз, сколько раз вы обновили браузер.

Если вы обновили URL три раза после первого посещения, ваш вывод будет выглядеть следующим образом:

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

Этот вывод показывает, что сетевой запрос отправляется на сервер API каждый раз, когда вы обновляете браузер. Если бы у вас было приложение с 1000 пользователями, обращающимися к той же конечной точке, это означало бы 1000 сетевых запросов, отправленных на сервер API.

Когда вы внедряете кэширование, запрос к API будет выполнен только один раз. Все последующие запросы будут получать данные из кэша, повышая производительность вашего приложения.

На данный момент остановите свой сервер Express с помощью CTRL+C.

Теперь, когда вы можете запрашивать данные из API и предоставлять их пользователям, вы будете кэшировать данные, возвращаемые из API, в Redis.

Шаг 3 — Кэширование запросов к RESTful API с использованием Redis

В этом разделе вы будете кэшировать данные из API таким образом, что только при первом посещении конечной точки вашего приложения будет запрошены данные с сервера API, а все последующие запросы будут извлекать данные из кэша Redis.

Откройте файл server.js:

  1. nano server.js

В вашем файле server.js импортируйте модуль node-redis:

fish_wiki/server.js

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

В том же файле подключитесь к Redis с использованием модуля node-redis, добавив выделенный код:

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

Сначала вы определяете переменную redisClient со значением undefined. После этого вы определяете анонимную самовызываемую асинхронную функцию, которая является функцией, запускающейся непосредственно после ее определения. Вы определяете анонимную самовызываемую асинхронную функцию, заключив безымянное определение функции в скобки (async () => {...}). Чтобы сделать ее самовызываемой, вы сразу после этого добавляете другой набор скобок (), что приводит к следующему виду (async () => {...})().

Внутри функции вы вызываете метод createClient() модуля redis, который создает объект redis. Поскольку вы не указали порт для использования Redis при вызове метода createClient(), Redis будет использовать порт 6379, порт по умолчанию.

Также вызывается метод on() Node.js, который регистрирует события на объекте Redis. Метод on() принимает два аргумента: error и обратный вызов. Первый аргумент error – это событие, срабатывающее, когда Redis сталкивается с ошибкой. Второй аргумент – это обратный вызов, который выполняется, когда событие error срабатывает. Этот обратный вызов записывает ошибку в консоль.

Наконец, вызывается метод connect(), который начинает соединение с Redis на порте по умолчанию 6379. Метод connect() возвращает обещание, поэтому вы используете синтаксис await, чтобы его разрешить.

Теперь, когда ваше приложение подключено к Redis, вы измените обратный вызов getSpeciesData(), чтобы сохранить данные в Redis при первом посещении и извлекать данные из кеша для всех последующих запросов.

В вашем файле server.js добавьте и обновите выделенный код:

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

В функции getSpeciesData вы определяете переменную isCached со значением false. Внутри блока try вызываете метод get() модуля node-redis с аргументом species. Когда метод находит ключ в Redis, соответствующий значению переменной species, он возвращает данные, которые затем присваиваются переменной cacheResults.

Затем оператор if проверяет, содержит ли переменная cacheResults данные. Если условие выполняется, переменной isCache присваивается значение true. Затем вызывается метод parse() объекта JSON с аргументом cacheResults. Метод parse() преобразует строковые данные JSON в объект JavaScript. После разбора JSON вызывается метод send(), который принимает объект с установленным свойством fromCache, равным переменной isCached. Метод отправляет ответ клиенту.

Если метод get() модуля node-redis не находит данных в кеше, переменная cacheResults устанавливается в null. В результате оператор if возвращает ложное значение. В этом случае выполнение переходит к блоку else, где вызывается функция fetchApiData() для получения данных из API. Однако после получения данных из API они не сохраняются в Redis.

Для сохранения данных в кеше Redis вам необходимо использовать метод set() модуля node-redis. Для этого добавьте выделенную строку:

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

Внутри блока else, после того как данные были получены, вы вызываете метод set() модуля node-redis для сохранения данных в Redis под ключом, равным значению переменной species.

Метод set() принимает два аргумента, которые являются парой ключ-значение: species и JSON.stringify(results).

Первый аргумент, species, – это ключ, под которым данные будут сохранены в Redis. Помните, что species устанавливается в значение, переданное в точку доступа, которую вы определили. Например, когда вы посещаете /fish/red-snapper, species устанавливается в red-snapper, который и будет ключом в Redis.

Второй аргумент, JSON.stringify(results), – это значение для ключа. В этом аргументе вы вызываете метод stringify() объекта JSON с переменной results в качестве аргумента, которая содержит данные, возвращенные из API. Метод преобразует JSON в строку; поэтому, когда вы получали данные из кэша, используя метод get() модуля node-redis ранее, вы вызывали метод JSON.parse с переменной cacheResults в качестве аргумента.

Ваш файл теперь будет выглядеть следующим образом:

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

Сохраните и закройте файл, а затем запустите server.js с помощью команды node:

  1. node server.js

После запуска сервера обновите http://localhost:3000/fish/red-snapper в вашем браузере.

Обратите внимание, что значение fromCache по-прежнему равно false:

Теперь обновите страницу еще раз, чтобы увидеть, что на этот раз fromCache установлено в true:

Обновите страницу пять раз и вернитесь в терминал. Ваш вывод будет похож на следующий:

Output
App listening on port 3000 Request sent to the API

Теперь сообщение Request sent to the API было зарегистрировано только один раз после нескольких обновлений URL, в отличие от последнего раздела, где сообщение было зарегистрировано при каждом обновлении. Этот вывод подтверждает, что на сервер был отправлен только один запрос и что далее данные были получены из Redis.

Чтобы дополнительно подтвердить, что данные хранятся в Redis, остановите свой сервер, используя CTRL+C. Подключитесь к клиенту сервера Redis следующей командой:

  1. redis-cli

Извлеките данные по ключу red-snapper:

  1. get red-snapper

Ваш вывод будет напоминать следующее (отредактировано для краткости):

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

Вывод показывает строковую версию данных JSON, которые API возвращает при посещении конечной точки /fish/red-snapper, что подтверждает, что данные API хранятся в кэше Redis.

Выйдите из клиента сервера Redis:

  1. exit

Теперь, когда вы можете кэшировать данные из API, вы также можете установить срок действия кэша.

Шаг 4 — Реализация срока действия кэша

При кешировании данных необходимо знать, как часто данные изменяются. Некоторые данные API изменяются в течение нескольких минут; другие – в течение часов, недель, месяцев или лет. Установка подходящего времени кеширования гарантирует, что ваше приложение предоставляет пользователям актуальные данные.

На этом этапе вы установите действительность кеша для данных API, которые должны храниться в Redis. Когда кеш истекает, ваше приложение отправляет запрос к API для получения свежих данных.

Вам необходимо ознакомиться с документацией вашего API, чтобы установить правильное время истечения кеша. В большинстве документаций будет упоминаться, с какой частотой данные обновляются. Однако есть случаи, когда документация не предоставляет этой информации, поэтому вам может прийтись догадаться. Проверка свойства last_updated различных конечных точек API может показать, с какой частотой данные обновляются.

После выбора продолжительности кеша вам необходимо преобразовать ее в секунды. Для демонстрации в этом руководстве вы установите продолжительность кеша в 3 минуты или 180 секунд. Эта примерная продолжительность упростит тестирование функционала продолжительности кеша.

Чтобы реализовать продолжительность действия кеша, откройте файл server.js:

  1. nano server.js

Добавьте выделенный код:

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

В методе set() модуля node-redis вы передаете третий аргумент объекта с следующими свойствами:

  • EX: принимает значение с продолжительностью кеша в секундах.
  • NX: если установлено в true, это гарантирует, что метод set() должен устанавливать только ключ, который еще не существует в Redis.

Сохраните и закройте ваш файл.

Вернитесь к клиенту сервера Redis, чтобы проверить корректность кэша:

  1. redis-cli

Удалите ключ red-snapper в Redis:

  1. del red-snapper

Выйдите из клиента Redis:

  1. exit

Теперь запустите сервер разработки с помощью команды node:

  1. node server.js

Переключитесь обратно в браузер и обновите URL http://localhost:3000/fish/red-snapper. В течение следующих трех минут, если вы обновите URL, вывод в терминале должен быть согласован с следующим выводом:

Output
App listening on port 3000 Request sent to the API

После того как пройдут три минуты, обновите URL в вашем браузере. В терминале вы должны увидеть, что “Запрос отправлен в API” был зарегистрирован дважды.

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

Этот вывод показывает, что кэш устарел, и запрос к API был сделан снова.

Вы можете остановить сервер Express.

Теперь, когда вы можете установить корректность кэша, вы будете кэшировать данные с помощью промежуточного ПО далее.

Шаг 5 — Кэширование данных в промежуточном ПО

На этом шаге вы будете использовать промежуточное ПО Express для кэширования данных. Промежуточное ПО — это функция, которая может получить доступ к объекту запроса, объекту ответа и обратному вызову, который должен запускаться после его выполнения. Функция, которая запускается после промежуточного ПО, также имеет доступ к объекту запроса и ответа. При использовании промежуточного ПО вы можете изменять объекты запроса и ответа или возвращать ответ пользователю раньше.

Для использования промежуточного ПО в вашем приложении для кэширования вы измените функцию обработчика getSpeciesData() для получения данных из API и их сохранения в Redis. Весь код, который ищет данные в Redis, будет перемещен в функцию промежуточного ПО cacheData.

При посещении конечной точки /fish/:species сначала будет запущена функция промежуточного ПО для поиска данных в кэше; если они найдены, будет возвращен ответ, и функция getSpeciesData не будет выполняться. Однако, если промежуточное ПО не найдет данные в кэше, оно вызовет функцию getSpeciesData для получения данных из API и их сохранения в Redis.

Сначала откройте ваш server.js:

  1. nano server.js

Затем удалите выделенный код:

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

В функции getSpeciesData() удалите весь код, который ищет данные, сохраненные в Redis. Также удалите переменную isCached, поскольку функция getSpeciesData() будет только получать данные из API и сохранять их в Redis.

После удаления кода установите fromCache в false, как показано ниже, чтобы функция getSpeciesData() выглядела следующим образом:

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

Функция getSpeciesData() извлекает данные из API, сохраняет их в кэше и возвращает ответ пользователю.

Затем добавьте следующий код для определения промежуточной функции для кэширования данных в 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) {
...
}
...

Функция промежуточного обработчика cacheData() принимает три аргумента: req, res и next. В блоке try функция проверяет, содержит ли переменная species данные, хранящиеся в Redis под ее ключом. Если данные есть в Redis, они возвращаются и устанавливаются в переменную cacheResults.

Затем оператор if проверяет, есть ли данные в cacheResults. Если условие истинно, данные сохраняются в переменную results. После этого промежуточное ПО использует метод send() для возврата объекта со свойствами fromCache, установленным в true, и data, установленным в переменную results.

Однако, если условие оператора if ложно, управление переходит к блоку else. Внутри блока else вызывается функция next(), которая передает управление следующей функции, которая должна выполниться после нее.

Чтобы промежуточное ПО cacheData() передавало управление функции getSpeciesData(), когда вызывается next(), соответственно обновите метод get() модуля express:

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

Метод get() теперь принимает cacheData в качестве своего второго аргумента, который является промежуточным ПО, ищущим данные, кешированные в Redis, и возвращающим ответ при нахождении.

Теперь, когда вы посещаете конечную точку /fish/:species, сначала выполняется cacheData(). Если данные кэшированы, он вернет ответ, и цикл запроса-ответа закончится здесь. Однако, если данные не найдены в кэше, будет вызвана getSpeciesData() для извлечения данных из API, их сохранения в кэше и возврата ответа.

Полный файл теперь будет выглядеть так:

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

Сохраните и закройте ваш файл.

Чтобы правильно протестировать кэширование, вы можете удалить ключ red-snapper в Redis. Для этого перейдите в клиент Redis:

  1. redis-cli

Удалите ключ red-snapper:

  1. del red-snapper

Выйдите из клиента Redis:

  1. exit

Теперь запустите файл server.js:

  1. node server.js

Как только сервер запустится, вернитесь в браузер и снова посетите http://localhost:3000/fish/red-snapper. Обновите его несколько раз.

Терминал будет записывать сообщение о том, что запрос был отправлен на API. Промежуточное ПО cacheData() будет обрабатывать все запросы в течение следующих трех минут. Ваш вывод будет выглядеть примерно так, если вы случайным образом обновите URL в течение четырех минут:

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

Поведение согласуется с тем, как работало приложение в предыдущем разделе.

Теперь вы можете кэшировать данные в Redis, используя промежуточное ПО.

Заключение

В этой статье вы создали приложение, которое получает данные из API и возвращает их в качестве ответа клиенту. Затем вы изменили приложение, чтобы кэшировать ответ API в Redis при первом посещении и обслуживать данные из кэша для всех последующих запросов. Вы изменили длительность кэширования так, чтобы она истекала после определенного времени, а затем использовали промежуточное ПО для обработки извлечения данных из кэша.

Как следующий шаг, вы можете изучить документацию по Node Redis, чтобы узнать больше о возможностях модуля node-redis. Вы также можете прочитать документацию по Axios и Express для более глубокого погружения в темы, рассмотренные в этом учебнике.

Чтобы продолжить развивать свои навыки в Node.js, ознакомьтесь с серией статей “Как писать код на Node.js”.

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