Saltar al contenidoPedro Farbo
Lección 16 / 2550 min

Caché con Redis

Cache con Redis

Redis mejora el rendimiento almacenando datos en memoria.

Instalación

bash
npm install ioredis

Docker

bash
docker run --name redis -p 6379:6379 -d redis:7-alpine

Configuración

typescript
// src/config/redis.tsimport Redis from 'ioredis'; export const redis = new Redis({  host: process.env.REDIS_HOST || 'localhost',  port: Number(process.env.REDIS_PORT) || 6379,  password: process.env.REDIS_PASSWORD,}); redis.on('connect', () => console.log('Redis conectado'));redis.on('error', (err) => console.error('Error Redis:', err));

Cache Service

typescript
// src/services/cache.service.tsimport { redis } from '../config/redis'; const DEFAULT_TTL = 3600; // 1 hora export class CacheService {  async get<T>(key: string): Promise<T | null> {    const data = await redis.get(key);    return data ? JSON.parse(data) : null;  }   async set(key: string, value: unknown, ttl: number = DEFAULT_TTL) {    await redis.set(key, JSON.stringify(value), 'EX', ttl);  }   async del(key: string) {    await redis.del(key);  }   async delPattern(pattern: string) {    const keys = await redis.keys(pattern);    if (keys.length > 0) {      await redis.del(...keys);    }  }} export const cacheService = new CacheService();

Uso en Services

typescript
// src/services/product.service.tsimport { cacheService } from './cache.service'; const CACHE_KEY = 'products';const CACHE_TTL = 1800; // 30 minutos export class ProductService {  async findAll(params: FindAllParams) {    const cacheKey = `${CACHE_KEY}:${JSON.stringify(params)}`;     // Intentar obtener del cache    const cached = await cacheService.get(cacheKey);    if (cached) {      return cached;    }     // Buscar en BD    const result = await this.queryProducts(params);     // Guardar en cache    await cacheService.set(cacheKey, result, CACHE_TTL);     return result;  }   async update(id: string, data: UpdateDTO) {    const product = await prisma.product.update({      where: { id },      data,    });     // Invalidar cache    await cacheService.delPattern(`${CACHE_KEY}:*`);     return product;  }}

Middleware de Cache

typescript
// src/middlewares/cache.middleware.tsimport { Request, Response, NextFunction } from 'express';import { cacheService } from '../services/cache.service'; export const cacheMiddleware = (ttl: number = 3600) => {  return async (req: Request, res: Response, next: NextFunction) => {    if (req.method !== 'GET') {      return next();    }     const key = `route:${req.originalUrl}`;    const cached = await cacheService.get(key);     if (cached) {      return res.json(cached);    }     // Interceptar res.json    const originalJson = res.json.bind(res);    res.json = (data: unknown) => {      cacheService.set(key, data, ttl);      return originalJson(data);    };     next();  };}; // Usorouter.get('/', cacheMiddleware(1800), controller.index);

Rate Limiting

typescript
// src/middlewares/rate-limit.middleware.tsimport { Request, Response, NextFunction } from 'express';import { redis } from '../config/redis';import { AppError } from '../errors/AppError'; export const rateLimit = (maxRequests: number, windowSeconds: number) => {  return async (req: Request, res: Response, next: NextFunction) => {    const ip = req.ip;    const key = `ratelimit:${ip}`;     const requests = await redis.incr(key);     if (requests === 1) {      await redis.expire(key, windowSeconds);    }     if (requests > maxRequests) {      throw new AppError('Demasiadas solicitudes', 429);    }     next();  };}; // Uso: 100 requests por minutoapp.use(rateLimit(100, 60));

Resumen

  • ✅ Redis conectado con ioredis
  • ✅ Cache service reutilizable
  • ✅ Invalidación de cache
  • ✅ Rate limiting

Próxima clase: Pruebas Unitarias! 🚀

¿Te gustó el contenido? ¡Tu contribución ayuda a mantener todo online y gratuito!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e