Cache com Redis
Redis melhora drasticamente a performance cacheando dados frequentemente acessados.
Instalação
bash
npm install ioredisConfiguraçã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! 🧪