APIs RESTful Avançadas com Express: Versionamento, Rate Limiting e Cache: Do Básico ao Avançado Já leu

Versionamento de APIs RESTful O versionamento é fundamental para manter compatibilidade com clientes enquanto evolui sua API. A estratégia mais comum é incluir a versão na URL, permitindo que diferentes versões coexistam no mesmo servidor. Com Express, implementamos isso facilmente usando middleware e roteadores separados. Este padrão permite deprecar versões antigas sem quebrar clientes existentes. Você pode adicionar middleware de aviso para versões antigas, notificando desenvolvedores sobre atualizações futuras. A chave é documentar claramente qual será o deadline para cada versão. Rate Limiting e Throttling Rate limiting protege sua API contra abuso e garante que recursos sejam distribuídos equitativamente entre clientes. O pacote é o padrão da indústria para esta tarefa. Implementaremos diferentes limites para diferentes rotas. Para produção com múltiplos servidores, use um store como Redis em vez de memória local. A biblioteca fornece que sincroniza limites entre instâncias. Sempre retorne headers informativos ( , ) para o cliente entender seu status. Cache Inteligente com Redis Cache reduz carga

Versionamento de APIs RESTful

O versionamento é fundamental para manter compatibilidade com clientes enquanto evolui sua API. A estratégia mais comum é incluir a versão na URL, permitindo que diferentes versões coexistam no mesmo servidor. Com Express, implementamos isso facilmente usando middleware e roteadores separados.

const express = require('express');
const app = express();

// Router V1
const routerV1 = express.Router();
routerV1.get('/users', (req, res) => {
  res.json({ version: 'v1', users: [{ id: 1, name: 'João' }] });
});

// Router V2 com estrutura diferente
const routerV2 = express.Router();
routerV2.get('/users', (req, res) => {
  res.json({ 
    version: 'v2', 
    data: { users: [{ id: 1, name: 'João', email: 'joao@email.com' }] },
    meta: { total: 1 }
  });
});

app.use('/api/v1', routerV1);
app.use('/api/v2', routerV2);

app.listen(3000);

Este padrão permite deprecar versões antigas sem quebrar clientes existentes. Você pode adicionar middleware de aviso para versões antigas, notificando desenvolvedores sobre atualizações futuras. A chave é documentar claramente qual será o deadline para cada versão.

Rate Limiting e Throttling

Rate limiting protege sua API contra abuso e garante que recursos sejam distribuídos equitativamente entre clientes. O pacote express-rate-limit é o padrão da indústria para esta tarefa. Implementaremos diferentes limites para diferentes rotas.

const rateLimit = require('express-rate-limit');

// Limite global: 100 requisições por 15 minutos
const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: 'Muitas requisições deste IP, tente novamente mais tarde.',
  standardHeaders: true,
  legacyHeaders: false,
});

// Limite mais rigoroso para login: 5 tentativas por 15 minutos
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
  keyGenerator: (req) => req.body.email || req.ip
});

// Limite suave para leitura: 1000 requisições por hora
const readLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 1000,
});

app.use(globalLimiter);

app.post('/login', loginLimiter, (req, res) => {
  // Lógica de autenticação
  res.json({ token: 'abc123' });
});

app.get('/data', readLimiter, (req, res) => {
  res.json({ data: 'informações públicas' });
});

Para produção com múltiplos servidores, use um store como Redis em vez de memória local. A biblioteca fornece RedisStore que sincroniza limites entre instâncias. Sempre retorne headers informativos (RateLimit-Limit, RateLimit-Remaining) para o cliente entender seu status.

Cache Inteligente com Redis

Cache reduz carga no servidor e melhora significativamente a latência. Implementaremos cache em camadas: algumas rotas com cache de curta duração, outras sem cache. Redis é ideal, mas também mostraremos cache em memória para desenvolvimento.

const redis = require('redis');
const client = redis.createClient();

// Middleware de cache genérico
const cacheMiddleware = (duracao = 300) => {
  return async (req, res, next) => {
    if (req.method !== 'GET') return next();

    const chave = `cache:${req.originalUrl}`;

    try {
      const dados = await client.get(chave);
      if (dados) {
        res.set('X-Cache', 'HIT');
        return res.json(JSON.parse(dados));
      }
    } catch (erro) {
      console.error('Erro ao acessar cache:', erro);
    }

    res.set('X-Cache', 'MISS');

    // Intercepta res.json original
    const jsonOriginal = res.json.bind(res);
    res.json = (dados) => {
      client.setEx(chave, duracao, JSON.stringify(dados))
        .catch(erro => console.error('Erro ao salvar cache:', erro));
      return jsonOriginal(dados);
    };

    next();
  };
};

// Rotas com cache de 5 minutos
app.get('/products', cacheMiddleware(300), (req, res) => {
  const produtos = [
    { id: 1, nome: 'Notebook', preco: 3000 },
    { id: 2, nome: 'Mouse', preco: 50 }
  ];
  res.json(produtos);
});

// Rota sem cache (dados em tempo real)
app.get('/stock/:id', (req, res) => {
  res.json({ stock: Math.floor(Math.random() * 100) });
});

// Invalidar cache ao atualizar
app.put('/products/:id', async (req, res) => {
  // Lógica de atualização

  // Invalida cache de produtos
  await client.del(`cache:/api/products`);

  res.json({ sucesso: true });
});

A estratégia correta é cachear dados que mudam infrequentemente e invalidar seletivamente. Use headers HTTP como Cache-Control: public, max-age=300 para que navegadores e proxies também façam cache, reduzindo requisições até sua API.

### Validação de Cache

Nem todos os dados devem ser cacheados. Adicione lógica condicional: dados confidenciais nunca devem entrar em cache, e endpoints que retornam diferentes dados por usuário precisam de chaves única por usuário.

const cacheSelectivo = (duracao = 300) => {
  return async (req, res, next) => {
    const usuario = req.user?.id;
    const ehPublico = !usuario && req.path.includes('/public');

    if (!ehPublico) return next();

    const chave = `cache:${req.originalUrl}`;
    const dados = await client.get(chave);
    if (dados) {
      res.set('X-Cache', 'HIT');
      return res.json(JSON.parse(dados));
    }

    next();
  };
};

Conclusão

Dominar versionamento permite evolução sustentável da API sem quebrar clientes. Rate limiting protege recursos e garante equidade entre usuários. Cache inteligente com Redis reduz latência e carga do servidor — mas sempre com invalidação estratégica. A combinação dessas três técnicas transforma uma API amadora em um serviço profissional, escalável e resiliente. Comece implementando em desenvolvimento, teste com carga realista e ajuste os parâmetros conforme observa o comportamento em produção.

Referências


Artigos relacionados