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.env
archivo
$ 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.env
expediente. Abre el.env
archivo 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.json
archivo 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.json
el punto de entrada a la aplicación esserver.js
. Crear elserver.js
usando 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.js
calculará el tiempo necesario para completar una solicitud.El blogRoutes importa un enrutador express desde el
./routes/route.blog.js
hora 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/blogs
será 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.js
Para 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/:id
punto final El:id
será 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/1
la 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.env
hora 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.crt
archivo 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 elioredis
paquete y los detalles de conexión del.env
archivo a través del archivo de secretos. Especificamos el parámetro cert en el objeto tls leyendo el contenido de redis-certificate.crt usandofs
mó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 usandocurl
usando 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:
Título del artículo Nombre (opcional) Correo electrónico (opcional) Descripción
Enviar sugerencia