Pular para o conteúdoPedro Farbo
Lição 16 / 2540 min

Cache com Redis

Cache com Redis

Redis melhora drasticamente a performance cacheando dados frequentemente acessados.

Instalação

bash
npm install ioredis

Configuração

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

Serviço de Cache

typescript
// src/services/cache.service.tsimport redis from '../config/redis'; export class CacheService {  private prefix = 'ecommerce:';   private getKey(key: string): string {    return `${this.prefix}${key}`;  }   async get<T>(key: string): Promise<T | null> {    const data = await redis.get(this.getKey(key));    return data ? JSON.parse(data) : null;  }   async set(key: string, value: any, ttl?: number): Promise<void> {    const data = JSON.stringify(value);     if (ttl) {      await redis.setex(this.getKey(key), ttl, data);    } else {      await redis.set(this.getKey(key), data);    }  }   async delete(key: string): Promise<void> {    await redis.del(this.getKey(key));  }   async deletePattern(pattern: string): Promise<void> {    const keys = await redis.keys(this.getKey(pattern));    if (keys.length) {      await redis.del(...keys);    }  }   async getOrSet<T>(key: string, fn: () => Promise<T>, ttl: number): Promise<T> {    const cached = await this.get<T>(key);     if (cached) {      return cached;    }     const data = await fn();    await this.set(key, data, ttl);    return data;  }}

Cache em Produtos

typescript
// src/services/product.service.tsimport { CacheService } from './cache.service'; const cacheService = new CacheService();const CACHE_TTL = 60 * 10; // 10 minutos export class ProductService {  async findAll(page: number = 1, limit: number = 20) {    const cacheKey = `products:list:${page}:${limit}`;     return cacheService.getOrSet(cacheKey, async () => {      const skip = (page - 1) * limit;       const [products, total] = await Promise.all([        prisma.product.findMany({          skip,          take: limit,          include: { category: true },          orderBy: { createdAt: 'desc' },        }),        prisma.product.count(),      ]);       return {        products,        pagination: {          page,          limit,          total,          totalPages: Math.ceil(total / limit),        },      };    }, CACHE_TTL);  }   async findById(id: string) {    return cacheService.getOrSet(`products:${id}`, async () => {      return prisma.product.findUnique({        where: { id },        include: { category: true, reviews: true },      });    }, CACHE_TTL);  }   async update(id: string, data: any) {    const product = await prisma.product.update({ where: { id }, data });     // Invalida caches relacionados    await cacheService.delete(`products:${id}`);    await cacheService.deletePattern('products:list:*');     return product;  }}

Middleware de Cache

typescript
// src/middlewares/cache.middleware.tsimport { Request, Response, NextFunction } from 'express';import { CacheService } from '../services/cache.service'; const cacheService = new CacheService(); export const cacheMiddleware = (ttl: number = 300) => {  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);    }     const originalJson = res.json.bind(res);     res.json = (data: any) => {      cacheService.set(key, data, ttl);      return originalJson(data);    };     next();  };}; // Usorouter.get('/products', cacheMiddleware(600), productController.list);

Rate Limiting com Redis

typescript
// src/middlewares/rate-limit.middleware.tsimport redis from '../config/redis';import { Request, Response, NextFunction } from 'express'; interface RateLimitOptions {  windowMs: number;  max: number;} export const rateLimiter = ({ windowMs, max }: RateLimitOptions) => {  return async (req: Request, res: Response, next: NextFunction) => {    const ip = req.ip || req.socket.remoteAddress;    const key = `ratelimit:${ip}`;     const current = await redis.incr(key);     if (current === 1) {      await redis.pexpire(key, windowMs);    }     if (current > max) {      return res.status(429).json({        error: 'Muitas requisições, tente novamente mais tarde',      });    }     res.setHeader('X-RateLimit-Limit', max);    res.setHeader('X-RateLimit-Remaining', Math.max(0, max - current));     next();  };};

Resumo

  • ✅ Conexão com Redis via ioredis
  • ✅ Serviço de cache genérico
  • ✅ Pattern getOrSet para cache automático
  • ✅ Invalidação de cache em atualizações
  • ✅ Rate limiting com Redis

Próxima aula: Testes Unitários! 🧪

Gostou do conteúdo? Sua contribuição ajuda a manter tudo online e gratuito!

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