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.