Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

ETag e Cache HTTP no Node.js

Atualizado em: 21 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

O ETag e Cache HTTP no Node.js reduzem tráfego e trabalho do servidor ao permitir que clientes, proxies e CDNs reutilizem respostas. Em vez de transferir o mesmo JSON ou arquivo novamente, o cliente pode validar uma cópia local e receber apenas um status 304 Not Modified quando o conteúdo não mudou.

Uma política de cache correta melhora latência e capacidade, mas uma configuração errada pode expor dados privados, servir conteúdo antigo ou misturar respostas entre usuários. É necessário combinar ETag, Cache-Control, Vary, Last-Modified e autenticação de acordo com o tipo de recurso.

Neste guia, você aprenderá a gerar ETags, responder a If-None-Match, configurar Cache-Control, trabalhar com conteúdo público e privado, evitar cache poisoning, integrar com APIs e testar comportamento condicional.

O que é cache HTTP?

Cache HTTP permite armazenar uma resposta e reutilizá-la durante um período ou depois de validação. O RFC 9111 sobre HTTP Caching define diretivas e comportamento. O RFC 9110 sobre semântica HTTP descreve validators e requisições condicionais.

Para clientes HTTP, consulte Fetch Nativo no Node.js. Para conexões reutilizadas, veja HTTP Agent no Node.js.

O que é ETag?

ETag é um identificador da representação atual de um recurso:

ETag: "user-42-revision-8"

Quando o cliente já possui essa versão, envia:

If-None-Match: "user-42-revision-8"

Se a representação não mudou, o servidor responde:

HTTP/1.1 304 Not Modified
ETag: "user-42-revision-8"

A resposta 304 não deve conter o corpo completo.

ETag forte

Um ETag forte indica equivalência byte a byte:

ETag: "a94a8fe5ccb19ba6"

Ele é adequado quando duas representações com o mesmo identificador possuem exatamente o mesmo conteúdo.

ETag fraco

Um ETag fraco começa com W/:

ETag: W/"revision-8"

Ele indica equivalência semântica suficiente para cache, mesmo que os bytes possam diferir, por exemplo por formatação.

Gerando ETag por hash

const { createHash } = require('node:crypto');

function createEtag(body) {
  const hash = createHash('sha256')
    .update(body)
    .digest('base64url');

  return `"${hash}"`;
}

Gerar hash exige ter o corpo disponível. Em respostas grandes ou streaming, prefira versão armazenada, checksum previamente calculado ou metadado do recurso.

ETag baseado em revisão

function createResourceEtag(resource) {
  return `"user-${resource.id}-v${resource.version}"`;
}

O campo version deve mudar em toda alteração que afeta a representação.

Comparando If-None-Match

function matchesIfNoneMatch(header, etag) {
  if (!header) return false;
  if (header.trim() === '*') return true;

  return header
    .split(',')
    .map(value => value.trim())
    .includes(etag);
}

A sintaxe completa possui detalhes de comparação forte e fraca. Bibliotecas do framework podem ser mais seguras do que um parser simplificado.

Resposta condicional

app.get('/api/users/:id', async (req, res) => {
  const user = await repository.findById(req.params.id);

  if (!user) {
    return res.status(404).json({
      code: 'USER_NOT_FOUND'
    });
  }

  const body = JSON.stringify(serializeUser(user));
  const etag = createEtag(body);

  res.set('ETag', etag);
  res.set('Cache-Control', 'private, max-age=0, must-revalidate');

  if (req.get('if-none-match') === etag) {
    return res.status(304).end();
  }

  res.type('application/json').send(body);
});

Para erros assíncronos em Express, consulte Express 5: erros assíncronos.

Cache-Control

Cache-Control define quem pode armazenar e por quanto tempo:

Cache-Control: public, max-age=60

Diretivas comuns:

  • public: caches compartilhados podem armazenar;
  • private: apenas cache do cliente;
  • no-store: não armazenar;
  • no-cache: pode armazenar, mas deve revalidar;
  • max-age: validade em segundos no cliente;
  • s-maxage: validade em cache compartilhado;
  • must-revalidate: não usar conteúdo expirado sem validação;
  • immutable: conteúdo não muda durante a validade.

no-cache não significa não armazenar

no-cache permite armazenamento, mas exige validação antes do uso. Para dados que nunca devem ser gravados em cache, use no-store.

Dados privados

Respostas com informações pessoais ou tokens não devem ser públicas:

Cache-Control: private, no-store

Uma resposta autenticada sem política correta pode ser armazenada por proxy compartilhado.

Conteúdo público

Catálogos públicos podem usar:

Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30

O navegador usa 60 segundos; a CDN pode usar 300 e servir conteúdo antigo por uma pequena janela enquanto atualiza.

Arquivos com hash no nome

/assets/app.a8f391c2.js

Quando o conteúdo muda, o nome muda. Isso permite:

Cache-Control: public, max-age=31536000, immutable

Não use validade anual para um arquivo que mantém o mesmo nome e muda de conteúdo.

Vary

Vary informa quais headers mudam a representação:

Vary: Accept-Encoding, Accept-Language

Sem Vary, o cache pode entregar conteúdo comprimido, idioma ou versão errada.

Vary e autenticação

Não use Vary: Authorization como única proteção de dados privados. Prefira private ou no-store.

Versões de API

Quando a versão está em header, inclua-a em Vary:

Vary: X-API-Version

Consulte Versionamento de API no Node.js.

Last-Modified

Last-Modified: Fri, 21 Aug 2026 13:30:00 GMT

O cliente pode enviar:

If-Modified-Since: Fri, 21 Aug 2026 13:30:00 GMT

Datas possuem precisão limitada e podem ser menos confiáveis que uma revisão explícita.

ETag e Last-Modified juntos

É possível enviar ambos. O cliente usa validators conforme as regras HTTP. ETag costuma ser a referência mais precisa.

If-Match para concorrência otimista

ETag também protege atualizações:

PUT /api/users/42
If-Match: "user-42-v8"

Se o recurso já está em v9, retorne 412 Precondition Failed. Isso evita sobrescrever mudanças de outra pessoa.

Atualização condicional

const expectedVersion = parseVersion(
  req.get('if-match')
);

const updated = await repository.updateIfVersionMatches({
  id,
  expectedVersion,
  changes
});

if (!updated) {
  return res.status(412).json({
    code: 'RESOURCE_CHANGED'
  });
}

If-None-Match com *

Em criação condicional:

PUT /api/documents/custom-id
If-None-Match: *

A operação deve ocorrer apenas se o recurso não existe.

Paginação e cache

Cada combinação de cursor, filtro e ordenação representa outra resposta. A chave de cache precisa incluir toda a query normalizada.

Veja Paginação em APIs Node.js.

Normalizando query strings

Estas URLs podem ser equivalentes:

?page=2&sort=name
?sort=name&page=2

Uma CDN pode tratá-las como chaves diferentes. Normalize parâmetros aceitos ou configure a camada de cache.

Cache no servidor

Além do protocolo HTTP, a aplicação pode armazenar resultados em memória ou Redis. Mesmo assim, continue enviando headers corretos para o cliente.

Cache stampede

Quando uma entrada expira, muitas requisições podem recalcular ao mesmo tempo. Estratégias:

  • stale-while-revalidate;
  • lock por chave;
  • jitter no TTL;
  • refresh antecipado;
  • limite de concorrência.

Invalidar cache

Invalidação pode ocorrer por:

  • mudança de versão;
  • evento do banco;
  • webhook;
  • publicação de conteúdo;
  • expiração curta;
  • purge na CDN.

Evite depender de remoção manual sem auditoria.

ETag de conteúdo comprimido

Uma representação gzip possui bytes diferentes da versão sem compressão. Um ETag forte pode precisar variar por encoding. Combine com Vary: Accept-Encoding ou use ETag fraco semântico.

Compressão

JSON e texto podem ser comprimidos pelo servidor ou proxy. Para fundamentos, consulte Zlib no Node.js.

Streaming

Gerar hash ao final de um stream impede enviar ETag antes dos headers. Para arquivos, use checksum armazenado; para relatórios dinâmicos, talvez ETag não seja apropriado.

Cache de erros

Alguns erros podem ser cacheados por proxies. Para falhas temporárias, defina política conservadora:

Cache-Control: no-store

Um 404 de recurso imutável pode receber TTL curto, se isso fizer sentido.

Cache poisoning

Um atacante tenta fazer o cache armazenar uma resposta maliciosa ou específica. Proteções:

  • validar Host e headers;
  • usar Vary correto;
  • normalizar URLs;
  • não refletir headers não confiáveis;
  • separar conteúdo autenticado;
  • configurar CDN explicitamente.

CORS e cache

Se Access-Control-Allow-Origin muda conforme Origin, use:

Vary: Origin

Consulte CORS em APIs Node.js.

CDN

Uma CDN pode sobrescrever ou ignorar diretivas. Verifique a política real, chaves consideradas, cookies removidos e comportamento de purge.

Documentação OpenAPI

Documente headers ETag, If-None-Match, If-Match e respostas 304 ou 412. Consulte OpenAPI com Node.js.

Observabilidade

Monitore:

  • cache hit e miss;
  • respostas 304;
  • bytes economizados;
  • tempo de geração;
  • invalidações;
  • respostas 412;
  • idade do conteúdo;
  • chaves de alta cardinalidade.

Logs

logger.info({
  route: req.route.path,
  cacheStatus: 'revalidated',
  statusCode: 304
}, 'request_completed');

Não registre ETags derivados de dados sensíveis se eles puderem revelar versão ou existência de recursos.

Testes

Cubra:

  • primeira resposta 200;
  • If-None-Match correspondente;
  • ETag antigo;
  • múltiplos ETags no header;
  • Vary;
  • conteúdo autenticado;
  • If-Match correto e incorreto;
  • compressão;
  • paginação;
  • invalidação.

Teste de revalidação

test('retorna 304 para ETag atual', async () => {
  const first = await request('/api/products/42');
  const etag = first.headers.etag;

  const second = await request('/api/products/42', {
    headers: {
      'if-none-match': etag
    }
  });

  assert.equal(second.status, 304);
  assert.equal(second.body.length, 0);
});

Erros comuns

  • Public em resposta privada: dados podem vazar.
  • no-cache interpretado como no-store: a política não faz o esperado.
  • Vary ausente: representações são misturadas.
  • ETag sem mudar: clientes usam conteúdo antigo.
  • Hash caro por requisição: cache aumenta CPU.
  • Arquivo imutável sem hash no nome: atualização demora a aparecer.
  • Cachear erro transitório: falha persiste.

Boas práticas

  • Classifique conteúdo como público ou privado.
  • Use no-store para dados sensíveis.
  • Gere ETag por revisão confiável.
  • Configure Vary.
  • Use nomes com hash para assets.
  • Teste 304 e 412.
  • Monitore hit rate.
  • Evite hash caro em streams.
  • Revise regras da CDN.
  • Documente validators.

Conclusão

O ETag e Cache HTTP no Node.js permitem reutilizar respostas e validar conteúdo sem transferi-lo novamente. ETag identifica a representação, enquanto Cache-Control define armazenamento, validade e revalidação.

A eficiência depende de segurança e precisão. Conteúdo privado deve permanecer fora de caches compartilhados, Vary precisa representar todas as diferenças e ETags devem mudar sempre que a resposta muda. Com testes condicionais e métricas de hit rate, cache HTTP reduz latência sem sacrificar consistência.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita