Cómo almacenar en caché las aplicaciones de Node.JS con Redis

El almacenamiento en caché es una parte crucial de la optimización del rendimiento de las aplicaciones web. El almacenamiento en caché implica el almacenamiento temporal de datos en un caché para reducir la cantidad de solicitudes realizadas a la base de datos y mejorar el tiempo de respuesta. Redis es un almacén de estructura de datos en memoria que se usa popularmente como una solución de almacenamiento en caché. Esta guía explora el uso de Redis como caché para una aplicación NodeJS con una base de datos de Postgres. Las bases de datos de Redis y Postgres se aprovisionarán a través de las bases de datos administradas por Vultr.

requisitos previos

  • Aprovisionar un servidor Ubuntu 20.04

  • Crear un usuario no root

  • Instale nodejs (use la versión PPA en la opción 2)

  • Cree una base de datos Postgres administrada por Vultr

  • Crear una base de datos Redis administrada por Vultr

  • instalar tmux

Configuración del entorno

Como referencia, la estructura de carpetas del proyecto final será la siguiente:

postgres-redis

|__configs

    | config.db.postgres.js

    | config.db.postgresPooling.js

    | config.db.redis.js

    | config.secrets.js

|__controllers

    | controller.blog.js

|__middlewares

    | middleware.timer.js

|__routes

    | route.blog.js

| .env

| server.js

| postgres-certificate.crt

| redis-certificate.crt

| package.json

| package-lock.json

Crear la carpeta del proyecto

$ mkdir postgres-redis

Cambiar el directorio a la carpeta del proyecto

$ cd postgres-redis

Crear.envarchivo

$ touch .env

Navegue a los Detalles de conexión del clúster de la base de datos PostgreSQL en la pestaña Información general. Agregue los detalles de la conexión a la.envexpediente. Abre el.envarchivo usando nano y reemplace los detalles de conexión de Postgres.

$ nano .env

Reemplace con las credenciales en la pestaña Descripción general de Postgres

PG_USERNAME= paste-username-here

PG_PASSWORD= paste-password-here

PG_DATABASE= paste-databse-here 

PG_PORT= paste-port-here

PG_HOST= paste-host-here

Vaya a los detalles de la conexión del clúster de la base de datos de Redis y agregue los detalles de la conexión de Redis al.env.

REDIS_USERNAME= paste-username-here

REDIS_PASSWORD= paste-password-here

REDIS_HOST= paste-host-here

REDIS_PORT= paste-port-here

Instalar dependencias

El proyecto requerirá las siguientes dependencias:

  • cors: el middleware para manejar el uso compartido de recursos de origen cruzado (CORS) permite el acceso a recursos de un dominio diferente.

  • express: un marco web para construir y administrar rutas, manejar solicitudes, middleware y muchas más funciones.

  • ioredis: un paquete para acceder a Redis.

  • nodemon: herramienta de desarrollo que reinicia automáticamente las aplicaciones del nodo al cambiar el archivo.

  • pg: paquete para acceder a PostgreSQL y realizar consultas SQL.

  • dotenv: paquete para gestionar variables de entorno en el .env.

Estas dependencias se pueden instalar usando el siguiente comando:

$ npm install cors express ioredis nodemon pg  dotenv

El comando anterior crea unpackage.jsonarchivo y unpackage-lock.json. En elpackage.json,edite el archivo usando nano, agregue server.js como el punto de entrada principal y agregue nodemon al comando de script de inicio como se muestra a continuación.

{                                                 



"main": "server.js",                              

{                                                 

"start":"nodemon server.js"                       



},                                                



    "dependencies": {                               

        "cors": "^2.8.5",                             

        "dotenv": "^16.0.3",                          

        "express": "^4.18.2",                         

        "ioredis": "^5.3.0",                          

        "nodemon": "^2.0.20",                         

        "pg": "^8.9.0"                                

    }                                               

}

Configurando el servidor

Como se especifica en elpackage.jsonel punto de entrada a la aplicación esserver.js. Crear elserver.jsusando nano

$ nano server.js

Los contenidos de este archivo son:

const express = require('express');

const cors = require('cors');

const responseTime = require('./middlewares/middleware.timer.js')



// import routes

const blogRoutes = require('./routes/route.blog.js');



const app = express();

app.use(express.json());

app.use(express.urlencoded({ extended: true }));

app.use(cors());

app.use(responseTime)

//  use routes 

app.use("/api/blogs", blogRoutes);

 // start server

const PORT = 3000;

app.listen(PORT, async () => {

console.log(`Server is running on port ${PORT}`);

});

El código hace lo siguiente:

  • Se importan paquetes express y cors.

  • El middleware de responseTime definido en el./middlewares/middleware.timer.jscalculará el tiempo necesario para completar una solicitud.

  • El blogRoutes importa un enrutador express desde el./routes/route.blog.jshora de oficina.

  • A continuación, creamos una instancia de una aplicación express y pasamos los middlewares express.json y express.urlencoded para analizar las solicitudes entrantes con datos codificados en URL y JSON, respectivamente.

  • El middleware cors se usa para manejar las políticas CORS y el middleware responseTime para registrar los tiempos de respuesta de las solicitudes.

  • El blogRoutes está montado en el/api/blogs/punto final Esto significa que cualquier solicitud enviada a/api/blogsserá manejado por los manejadores de ruta de./routes/route.blog.js.

Middleware de tiempo de respuesta

Cree el middleware de responseTime en el./middlewares/middleware.timer.js.

$ mkdir middlewares && cd middlewares && nano middleware.timer.js

El código en el archivo es el siguiente

const responseTime = ((req, res, next)=>{

const startTime = process.hrtime();

res.on('finish', ()=>{

const totalTime = process.hrtime(startTime);

const totalTimeInMs = totalTime[0] * 1000 + totalTime[1] / 1e6;

console.log(`The request to /api/blogs${req.url} took `,totalTimeInMs , " ms");

 });

next();

 })

module.exports = responseTime;

El código mide el tiempo que lleva manejar una solicitud realizada al punto final ‘/api/blogs’. El middleware agrega un temporizador a cada solicitud e inicia un temporizador tan pronto como se invoca el middleware, y la hora de finalización se registra en el evento ‘finalizar’ del objeto de respuesta. El tiempo transcurrido se calcula restando la hora de inicio de la hora de finalización y se expresa en milisegundos. El tiempo empleado se registra en la consola.

Rutas

Las rutas GET y POST específicas se definen en el./routes/route.blog.jsPara crear el archivo, navegue de regreso a la carpeta del proyecto.

$  cd ..

Cree el archivo de rutas usando el siguiente comando.

$ mkdir routes && cd routes && nano route.blog.js

El código de este archivo es el siguiente:

const express = require('express');

const router = express.Router();

const { getBlogs ,createBlog} = require('../controllers/controller.blog.js');

router.get('/:id', getBlogs);

router.post("https://www.vultr.com/", createBlog);

module.exports = router;
  • El código configura un enrutador Express e importa las funciones de la ciudad desde../controllers/controller.blog.js. Las dos funciones son getBlogs y createBlog.

  • La función getBlogs se monta en la solicitud GET en el/api/blogs/:idpunto final El:idserá un parámetro de URL dinámico para la identificación específica que se está recuperando. Por ejemplo, una solicitud GET para/api/blogs/1la función getBlogs se ejecutaría con req.params.id igual a 1 .

  • La función createBlog se monta en la solicitud POST en el/api/blogs/punto final Esta función manejará todas las solicitudes al punto final.

Secretos de configuración

Importamos todas las variables de entorno en un solo archivo para facilitar la gestión cuando es necesario realizar cambios.

Vuelva a la carpeta del proyecto.

$ cd ..

Cree el archivo de secretos con el siguiente comando.

$ mkdir configs && cd configs && nano config.secrets.js

El código en este archivo importa todas las variables que definimos anteriormente en el.env.

const dotenv = require('dotenv');

const path = require('path');

dotenv.config({ path: path.resolve(__dirname, '../.env') });



exports.secrets = {



PG_USERNAME: process.env.PG_USERNAME,

PG_PASSWORD: process.env.PG_PASSWORD,

PG_DATABASE: process.env.PG_DATABASE,

PG_PORT: process.env.PG_PORT,

PG_HOST: process.env.PG_HOST,

REDIS_USERNAME: process.env.REDIS_USERNAME,

REDIS_PASSWORD: process.env.REDIS_PASSWORD,

REDIS_PORT: process.env.REDIS_PORT,

REDIS_HOST: process.env.REDIS_HOST,



}

El paquete dotenv se usa para leer el.envhora de oficina.

Guardar certificados firmados

Necesitamos guardar los certificados firmados para habilitar SSL tanto para Redis como para Postgres. En la pestaña Descripción general de Postgres, descargue el certificado firmado

https://cdn.hashnode.com/res/hashnode/image/upload/v1675264831640/671ac5ef-762d-4222-ade8-3ac928259638.png?auto=compress,format&format=webp

En la carpeta del proyecto, cree postgres-certificate.crt y pegue el contenido del certificado descargado en este archivo. Primero navegue de regreso a la carpeta del proyecto

$ cd ..

A continuación, creapostgres-certificate.crtarchivo y pegue los detalles del certificado usando el siguiente comando

$ nano postgres-certificate.crt

Haga lo mismo con el certificado firmado de Redis y guárdelo en elredis-certificate.crt.

$ nano redis-certificate.crt

Conexión PostgreSQL

La conexión de Postgres se realiza mediante el paquete pg. En la carpeta del proyecto, cree el archivo de conexión de Postgres usando el siguiente comando:

$ cd configs && nano config.db.postgres.js

El código en el archivo es el siguiente:

const { Client } = require('pg');

const { secrets } = require('./config.secrets.js');

const fs = require('fs');



let client;



exports.connectPG = (async () => {

if (client) return client;

try{

    console.log('Connecting to Postgres...');

    const client = new Client({

        user: secrets.PG_USERNAME,

        host: secrets.PG_HOST,

        database: secrets.PG_DATABASE,

        password:   secrets.PG_PASSWORD,

        port: secrets.PG_PORT,

        ssl: {

            rejectUnauthorized: false,

            cert: fs.readFileSync('postgres-certificate.crt').toString(),

          },

    });



    await client.connect();

    console.log('Connected to Postgres!');



    return client;

}catch(err){

    console.error(err);

}



})();

Importamos la clase Client de la biblioteca pg, las variables ambientales en el archivo secrets y el módulo fs. El módulo fs se usa para leer el contenido del archivo en NodeJS. Inicializamos la variable del cliente y verificamos si el objeto del cliente ya existe cuando se llama a la función connectPG. Esto permite reutilizar las conexiones de Postgres en lugar de realizar una nueva conexión en cada solicitud. Cuando el objeto de cliente no existe, se crea un objeto de cliente utilizando los detalles de conexión de las variables ambientales. La biblioteca fs lee el contenido de postgres-certificate.crt y lo agrega a la propiedad SSL. Por último, nos conectamos a Postgres y devolvemos el objeto cliente de la conexión. La función es autoinvocada.

Conexión Redis

La conexión redis sigue un patrón similar a la conexión de Postgres. Cree el archivo redis desde la carpeta del proyecto usando el siguiente comando

$ cd configs && nano config.db.redis.js

El código en el archivo es el siguiente

const Redis = require('ioredis');

const { secrets } = require('./config.secrets.js');

const fs = require('fs');



let client;

exports.redisDB = (async () => {

 if (client) return client;

  try {



    console.log('Connecting to Redis DB...');

    const client = new Redis({

        username: secrets.REDIS_USERNAME,

        host: secrets.REDIS_HOST,

        port: secrets.REDIS_PORT,

        password: secrets.REDIS_PASSWORD,

        tls: {

            cert: fs.readFileSync('redis-certificate.crt', 'ascii')

        }

    });



    await client.on('connect', () => {

        console.log('Connected to Redis!');

    });



    await client.on('error', (err) => {

        console.error("Redis error ",err);

    });



    return client;

    } catch (err) {

      console.error(err);

  }

})()

El código se conecta a redis usando elioredispaquete y los detalles de conexión del.envarchivo a través del archivo de secretos. Especificamos el parámetro cert en el objeto tls leyendo el contenido de redis-certificate.crt usandofsmódulo.

controladores

El controlador une todo. El controlador tendrá dos funciones. Una función manejará la solicitud GET y la otra manejará las solicitudes POST mientras interactúa con las dos bases de datos administradas.

Mientras está en la carpeta del proyecto, cree el archivo del controlador:

$ mkdir controllers && cd controllers && nano controller.blog.js

El código de este archivo es el siguiente:

const { redisDB } = require('../configs/config.db.redis.js');

const { connectPG } = require('../configs/config.db.postgres.js');



const getBlogs = async (req, res) => {

const { id } = req.params;



 try {



const redisClient = await redisDB;

const jsonString = await redisClient.get(id);



if (jsonString !== null) {

  console.log('Data from Redis');

  const dataFromRedis = JSON.parse(jsonString);

  return res.status(200).json(dataFromRedis);

}



const pool = await connectPG;

const { rows } = await pool.query(`SELECT * FROM blogs`);

console.log("Data from postgres");

await redisClient.set(id, JSON.stringify(rows));

res.status(200).json(rows)

  } catch (error) {

     console.error(error);

     res.status(500).json({ error: 'Error while retrieving blog' });

     }

} ;



const createBlog = async (req, res) => {

 try {

 const client = await connectPG;

 await client.query(`

    CREATE TABLE IF NOT EXISTS blogs (

    id SERIAL PRIMARY KEY,

    title VARCHAR(255) NOT NULL,

    content TEXT NOT NULL,

    created_at TIMESTAMP NOT NULL DEFAULT NOW(),

    updated_at TIMESTAMP NOT NULL DEFAULT NOW()

   )`);

   const query = `INSERT INTO blogs (title, content)

                   VALUES ($1, $2) RETURNING *;`;

   const values = [req.body.title, req.body.content];



  const blogId = await client.query(query, values);

  res.status(200).json(`Blog ${blogId.rows[0].id} saved!`);

  } catch (error) {

   console.error(error);

  res.status(500).json({ error: 'Unable to retrieve blogs' });

   } 

 };





module.exports = { getBlogs, createBlog };

El código hace lo siguiente:

  • Importamos las conexiones Redis y Postgres.

  • La función getBlogs maneja las solicitudes GET realizadas a la ruta /api/blogs/:id. La función desestructura el valor de identificación del objeto de parámetros de solicitud. A continuación, se establece la conexión del cliente de Redis y verificamos si el blog con la identificación está en la base de datos de Redis. Si los datos existen, los devolvemos al cliente. Si los datos no están en Redis, nos conectamos a Postgres y recuperamos los datos de la tabla de blogs con la identificación igual al parámetro de solicitud. Por último, almacenamos los datos en Redis para evitar tener que recuperar los datos de Postgres en solicitudes posteriores.

  • La función createBlog connects maneja las solicitudes POST realizadas. Esta función crea una tabla de blog si no existe en Postgres. La tabla tiene cinco filas; id, título, contenido, createdat y updatedat. Recuperamos el título y el contenido del blog del cuerpo de la solicitud y los insertamos en la tabla del blog. Cuando esta operación tiene éxito, devolvemos un mensaje de éxito al cliente.

  • Por último, los controladores se exportan.

Creación de blogs y pruebas de tiempos de respuesta.

Ahora que tenemos todo el código necesario, podemos probar la API. Inicie una sesión de tmux y cree dos ventanas. En la primera ventana, ejecute la API mientras está en la carpeta del proyecto usando el siguiente comando:

$ npm start

Deberías ver el siguiente resultado

https://cdn.hashnode.com/res/hashnode/image/upload/v1675276143139/84210d1b-aee2-4f76-b059-cde85615e9b6.png?auto=compress,format&format=webp

En la segunda ventana de tmux, crea un blog usandocurlusando el comando:

$ curl -X POST -H "Content-Type: application/json" -d '{"title":"Vultr  blog one","content":" This is  blog one"}' https://localhost:3000/api/blogs/

De forma predeterminada, la identificación de este blog será 1; por lo tanto, en la segunda ventana de tmux, podemos usar esta identificación para obtener datos de la base de datos usando curl.

$ curl https://localhost:3000/api/blogs/1

La repetición de esta solicitud GET en la ventana dos muestra que el tiempo necesario para recuperar el blog 1 se ha reducido en 496 milisegundos. Por lo tanto, el almacenamiento en caché ha reducido significativamente el tiempo de respuesta.

Más recursos:

documentación de la red

Título del artículo Nombre (opcional) Correo electrónico (opcional) Descripción

Enviar sugerencia

Artículos Relacionados