Como usar caching de resposta com ETags e Cache-Control em APIs
1. Fundamentos do Caching em APIs REST
O caching de resposta é uma das técnicas mais eficazes para otimizar APIs REST. Quando implementado corretamente, reduz a latência das requisições, economiza banda de rede e diminui a carga no servidor. Em cenários de alta demanda, o caching pode reduzir o tempo de resposta de centenas de milissegundos para alguns poucos milissegundos.
Existem três níveis principais de caching:
- Caching no cliente: o navegador ou aplicativo armazena respostas localmente
- Caching em proxy reverso: servidores como Varnish, Nginx ou CDNs armazenam respostas intermediárias
- Caching no servidor: o próprio servidor da API mantém um cache interno (ex.: Redis, Memcached)
Os cabeçalhos HTTP fundamentais para caching são:
Cache-Control: define diretivas de armazenamento e validadeETag: identificador único da versão do recursoLast-Modified: timestamp da última modificaçãoExpires: data de expiração (obsoleto em favor demax-age)
2. Configurando Cache-Control para Controle de Validade
O cabeçalho Cache-Control é o principal mecanismo para controlar o comportamento do cache. Suas diretivas mais importantes são:
Cache-Control: public, max-age=3600
Cache-Control: private, max-age=300
Cache-Control: no-cache, no-store, must-revalidate
Diretivas principais:
public: qualquer cache (incluindo proxies) pode armazenar a respostaprivate: apenas o cache do cliente pode armazenar (navegador, não proxies)no-cache: força revalidação com o servidor antes de usar cacheno-store: proíbe completamente o armazenamento em cachemax-age: tempo máximo em segundos que o recurso é considerado frescos-maxage: similar amax-age, mas aplica-se apenas a caches compartilhados (proxies/CDNs)
Estratégia para recursos estáticos vs. dinâmicos:
# Recurso estático (imagem, CSS, JS versionado)
Cache-Control: public, max-age=31536000, immutable
# Recurso dinâmico (lista de usuários)
Cache-Control: private, max-age=60
# Endpoint de autenticação
Cache-Control: no-store
Combinando com Vary:
O cabeçalho Vary informa que o cache deve considerar certos cabeçalhos para diferenciar versões:
Cache-Control: public, max-age=3600
Vary: Accept-Encoding, Authorization
Isso garante que versões comprimidas e não comprimidas do recurso sejam cacheadas separadamente, e que respostas para diferentes usuários não se misturem.
3. Implementando ETags para Validação de Recursos
ETags (Entity Tags) são identificadores únicos que representam o estado de um recurso. Quando o recurso muda, a ETag muda. O fluxo funciona assim:
- Servidor enha
ETag: "abc123"na resposta - Cliente armazena a ETag e, na próxima requisição, envia
If-None-Match: "abc123" - Servidor compara: se o recurso não mudou, retorna
304 Not Modifiedsem corpo - Se mudou, retorna
200 OKcom novo recurso e nova ETag
Gerando ETags:
# ETag forte (baseada em hash do conteúdo)
ETag: "d41d8cd98f00b204e9800998ecf8427e"
# ETag fraca (prefixo W/)
ETag: W/"1234567890"
# ETag baseada em timestamp + versão
ETag: "v2-1689023456"
Exemplo de fluxo completo:
Requisição inicial:
GET /api/users/123 HTTP/1.1
Host: api.exemplo.com
Resposta:
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "a1b2c3d4"
Cache-Control: private, max-age=0, must-revalidate
{"id": 123, "nome": "João"}
Requisição subsequente:
GET /api/users/123 HTTP/1.1
Host: api.exemplo.com
If-None-Match: "a1b2c3d4"
Resposta (recurso inalterado):
HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"
Cache-Control: private, max-age=0, must-revalidate
4. Estratégias Avançadas de Cache-Control para APIs
stale-while-revalidate e stale-if-error:
Permitem servir conteúdo obsoleto enquanto o cache é atualizado em segundo plano:
Cache-Control: public, max-age=3600, stale-while-revalidate=300, stale-if-error=86400
stale-while-revalidate=300: por 5 minutos após expirar, serve cache e revalida em backgroundstale-if-error=86400: por 24 horas, se o servidor falhar, serve cache obsoleto
Diretiva immutable:
Para recursos que nunca mudam (assets com hash no nome):
Cache-Control: public, max-age=31536000, immutable
Isso evita que o cliente sequer tente revalidar durante o período de validade.
APIs públicas vs. privadas:
# API pública (sem autenticação)
Cache-Control: public, max-age=300
# API privada (com token de autenticação)
Cache-Control: private, max-age=60
Vary: Authorization
5. Integração de ETags com Cache-Control na Prática
Implementação no servidor (Node.js/Express):
const crypto = require('crypto');
function cacheMiddleware(req, res, next) {
// Configurar Cache-Control
res.set('Cache-Control', 'private, max-age=0, must-revalidate');
// Interceptar res.json para gerar ETag
const originalJson = res.json.bind(res);
res.json = function(body) {
const content = JSON.stringify(body);
const etag = crypto.createHash('md5').update(content).digest('hex');
// Verificar If-None-Match
const clientEtag = req.headers['if-none-match'];
if (clientEtag && clientEtag === `"${etag}"`) {
return res.status(304).end();
}
res.set('ETag', `"${etag}"`);
return originalJson(body);
};
next();
}
app.get('/api/users/:id', cacheMiddleware, (req, res) => {
const user = buscarUsuario(req.params.id);
res.json(user);
});
Consumo no cliente (fetch):
let cachedEtag = null;
async function buscarUsuario(id) {
const headers = {};
if (cachedEtag) {
headers['If-None-Match'] = cachedEtag;
}
const response = await fetch(`/api/users/${id}`, { headers });
if (response.status === 304) {
// Usar dados em cache local
return dadosLocais;
}
const data = await response.json();
cachedEtag = response.headers.get('ETag');
return data;
}
ETags fracas vs. fortes:
- ETag forte (
"abc123"): representa exatamente o mesmo conteúdo byte a byte - ETag fraca (
W/"abc123"): representa o mesmo significado semântico, mas não necessariamente idêntico byte a byte
Use ETags fracas quando o recurso pode ter diferenças irrelevantes (ex.: formatação de JSON, espaços em branco).
6. Cache em APIs com Dados Dinâmicos e Autenticação
Caching seguro para endpoints autenticados:
Cache-Control: private, max-age=60
Vary: Authorization
A diretiva private impede que proxies armazenem a resposta, e Vary: Authorization garante que cada token de autenticação tenha sua própria entrada no cache.
Caching de listas paginadas:
GET /api/users?page=2&limit=20&sort=nome
Cache-Control: public, max-age=120
Vary: Accept-Encoding
Para listas com muitos parâmetros de consulta, considere usar um cache baseado em URL completa no servidor.
Invalidando cache:
# Purge manual (se o cache suportar)
PURGE /api/users/123
# Versionamento de recursos
GET /api/v2/users/123
# TTLs curtos para dados voláteis
Cache-Control: public, max-age=30
7. Monitoramento e Depuração de Cache
Usando cURL para inspecionar cabeçalhos:
curl -I https://api.exemplo.com/users/123
# Resposta:
HTTP/2 200
cache-control: private, max-age=0, must-revalidate
etag: "a1b2c3d4"
age: 0
# Com If-None-Match:
curl -H "If-None-Match: \"a1b2c3d4\"" -I https://api.exemplo.com/users/123
# Resposta:
HTTP/2 304
etag: "a1b2c3d4"
Cabeçalho Age:
Indica há quantos segundos o recurso está em cache:
Age: 120
Logs de servidor:
[INFO] GET /api/users/123 - 304 (cache hit) - 2ms
[INFO] GET /api/products - 200 (cache miss) - 150ms
8. Boas Práticas e Armadilhas Comuns
Evitar cache de erros:
# Incorreto
Cache-Control: public, max-age=3600
HTTP/1.1 500 Internal Server Error
# Correto
Cache-Control: no-store
HTTP/1.1 500 Internal Server Error
Cuidado com Vary: *:
# Evite - invalida completamente o cache
Vary: *
# Prefira especificar apenas os cabeçalhos relevantes
Vary: Accept-Encoding, Authorization
Quando não usar ETags:
- Recursos que mudam a cada requisição (ex.: timestamps, contadores)
- Payloads muito grandes (a geração do hash consome CPU)
- Endpoints que retornam dados diferentes para cada usuário (use
privateeno-cache)
Resumo de boas práticas:
- Sempre defina
Cache-Controlexplicitamente - Use
ETagpara revalidação eficiente - Para recursos estáticos, use
immutablecommax-agelongo - Para APIs autenticadas, use
privateeVary: Authorization - Nunca cacheie respostas de erro sem
no-store - Monitore a taxa de cache hit/miss para ajustar TTLs
Referências
- MDN Web Docs: HTTP Caching — Documentação completa sobre cabeçalhos de cache HTTP, incluindo Cache-Control, ETag e estratégias de validação
- RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching — Especificação oficial do IETF sobre caching HTTP, definindo todo o comportamento de caches
- Google Web Fundamentals: HTTP Caching — Guia prático do Google sobre caching HTTP com exemplos de configuração para APIs e sites
- Fastly: Caching Everything with Varnish — Tutorial avançado sobre Cache-Control e ETags em CDNs e proxies reversos
- Express.js: ETag Configuration — Documentação oficial do Express.js sobre configuração de ETags, incluindo opções de geração forte e fraca
- Cloudflare: Caching Best Practices — Guia de melhores práticas para caching em APIs usando Cloudflare CDN e cabeçalhos HTTP
- HTTP Archive: Cache Control Stats — Estatísticas reais de uso de Cache-Control na web, mostrando padrões e tendências atuais