Server-Sent Events em JavaScript (EventSource)
Aprenda Server-Sent Events em JavaScript com a API EventSource — receba um fluxo unidirecional de atualizações do servidor via HTTP, com reconexão automática e eventos nomeados.
Server-Sent Events (SSE) são uma forma padrão de o servidor enviar um fluxo contínuo e unidirecional de atualizações de texto ao navegador através de uma única conexão HTTP persistente. Em vez de o cliente fazer polling repetidamente em um endpoint com fetch perguntando "há algo novo?", a conexão permanece aberta e o servidor escreve mensagens sempre que tem algo para enviar. O navegador expõe isso através da interface nativa EventSource, dispensando qualquer biblioteca externa para consumir o fluxo.
O SSE brilha quando as atualizações fluem em uma única direção — do servidor para o cliente. Feeds de notícias ao vivo, notificações, barras de progresso de build ou upload, painéis de monitoramento e cotações de ações são casos de uso naturais.
Recebendo um fluxo com EventSource
Abrir um fluxo requer uma única linha. Você cria um EventSource apontado para um endpoint do servidor e, em seguida, anexa handlers para os eventos que ele dispara.
const es = new EventSource('/events');
es.onmessage = (event) => {
console.log('New message:', event.data);
};
es.onopen = () => {
console.log('Connection opened');
};
es.onerror = (error) => {
console.log('Connection error:', error);
};Alguns pontos importantes:
- A conexão é aberta assim que você constrói o
EventSource. onmessageé disparado para cada mensagem padrão (sem nome) que o servidor envia. O payload é sempre uma string, disponível emevent.data.onopené disparado uma vez quando a conexão é estabelecida (e novamente após uma reconexão).onerroré disparado quando a conexão cai ou falha. Ao contrário de umfetchcom falha, isso não significa necessariamente que você deve desistir — o navegador geralmente tenta reconectar automaticamente (mais sobre isso abaixo).
Aqui está um exemplo um pouco mais completo que analisa payloads JSON e atualiza a página:
const es = new EventSource('/notifications');
es.onmessage = (event) => {
const data = JSON.parse(event.data);
const li = document.createElement('li');
li.textContent = data.message;
document.querySelector('#feed').append(li);
};O formato de dados
O servidor responde com o tipo de conteúdo text/event-stream e escreve texto UTF-8 simples em um formato baseado em linhas. Cada mensagem é um bloco de linhas campo: valor, e uma linha em branco marca o fim de uma mensagem.
data: Hello there
data: First line
data: Second line
event: priceUpdate
data: {"symbol":"ACME","price":42.10}
id: 42
data: This message has an id
retry: 10000
data: Reconnect after 10 seconds next timeOs campos reconhecidos são:
data:— o payload da mensagem. Se uma mensagem tiver múltiplas linhasdata:, elas serão unidas com uma nova linha (\n) antes de serem entregues comoevent.data.event:— um nome de evento opcional. Sem ele, a mensagem é entregue aoonmessage.id:— um identificador opcional que o navegador memoriza para reconexão.retry:— o atraso de reconexão em milissegundos.
Linhas que começam com dois-pontos (:) são comentários e são ignoradas — os servidores frequentemente as enviam como pings de keep-alive.
Eventos nomeados
Por padrão, todas as mensagens vão para onmessage. Um servidor pode, em vez disso, rotular uma mensagem com um campo event:, e o cliente escuta esse nome específico com addEventListener. Isso permite rotear diferentes tipos de atualização para diferentes handlers pela mesma conexão.
Dado um fluxo como este:
event: priceUpdate
data: {"symbol":"ACME","price":42.10}
event: newsAlert
data: Markets closed early today
data: a plain default messageVocê configuraria o cliente da seguinte forma:
const es = new EventSource('/market');
es.addEventListener('priceUpdate', (event) => {
const { symbol, price } = JSON.parse(event.data);
console.log(`${symbol} is now ${price}`);
});
es.addEventListener('newsAlert', (event) => {
console.log('News:', event.data);
});
// Messages with no `event:` field still land here.
es.onmessage = (event) => {
console.log('Default message:', event.data);
};Reconexão automática
Este é o recurso que diferencia o SSE de implementar seu próprio streaming com fetch. Se a conexão cair — uma rede instável, reinicialização do servidor, timeout de proxy — o navegador reconecta automaticamente após um breve atraso. Você não precisa escrever nenhum loop de tentativas.
Para tornar a retomada confiável, o navegador rastreia o último id: recebido. Na reconexão, envia esse valor de volta em um cabeçalho de requisição Last-Event-ID, para que o servidor possa continuar exatamente de onde o fluxo parou, em vez de reproduzir tudo desde o início.
GET /events HTTP/1.1
Last-Event-ID: 42
Accept: text/event-streamO servidor também pode ajustar o atraso antes da próxima reconexão enviando um campo retry: (em milissegundos):
retry: 5000
data: The browser will wait 5 seconds before reconnecting if droppedA reconexão automática só ocorre enquanto a conexão é considerada recuperável. Se o servidor responder a uma reconexão com um erro HTTP como 204 No Content ou um status 4xx, o navegador trata o fluxo como encerrado e para de tentar.
Estado da conexão e fechamento
Um EventSource expõe seu estado atual através da propriedade readyState, que corresponde a três constantes:
EventSource.CONNECTING(0) — conectando ou aguardando para reconectar.EventSource.OPEN(1) — conectado e recebendo.EventSource.CLOSED(2) — fechado e não reconectará.
Quando você não precisar mais do fluxo, chame es.close(). Isso encerra a conexão imediatamente e — importante — o navegador não reconectará depois disso.
const es = new EventSource('/events');
// Stop listening after 30 seconds.
setTimeout(() => {
es.close();
console.log('readyState is now', es.readyState); // 2 (CLOSED)
}, 30000);Requisições de origem cruzada e credenciais
Por padrão, um EventSource segue a política de mesma origem. Para transmitir a partir de uma origem diferente, o servidor deve enviar os cabeçalhos CORS apropriados (como Access-Control-Allow-Origin).
Cookies não são enviados em requisições de origem cruzada, a menos que você opte por isso. Passe a opção withCredentials e certifique-se de que o servidor permite credenciais em sua configuração CORS:
const es = new EventSource('https://api.example.com/events', {
withCredentials: true,
});Uma nota sobre o lado do servidor
O SSE é principalmente um recurso do navegador, mas o servidor precisa cooperar. Qualquer linguagem que você use, a receita é a mesma: responda com Content-Type: text/event-stream, mantenha a conexão aberta em vez de encerrar a resposta e escreva blocos formatados como eventos à medida que os dados ficam disponíveis.
Aqui está um exemplo mínimo com Node.js para ilustrar a estrutura:
import http from 'node:http';
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
let id = 0;
const timer = setInterval(() => {
id += 1;
res.write(`id: ${id}\n`);
res.write(`data: The time is ${new Date().toISOString()}\n\n`);
}, 1000);
// Stop the timer when the client disconnects.
req.on('close', () => clearInterval(timer));
}).listen(3000);Cada bloco termina com uma linha em branco (\n\n) para completar a mensagem. O cliente conectado com new EventSource('http://localhost:3000') receberia uma atualização por segundo.
Limitações
O SSE transmite apenas texto (UTF-8) — não há tipo de frame binário, portanto você não pode transmitir bytes brutos como imagens ou áudio sem codificá-los primeiro. Ele também é estritamente unidirecional: para enviar dados ao servidor você ainda precisa de uma requisição normal via fetch ou XMLHttpRequest, ou de uma conexão WebSocket full-duplex.
Mais um limite prático: sobre HTTP/1.1, os navegadores limitam o número de conexões simultâneas a uma única origem em torno de seis. Como cada EventSource mantém uma conexão aberta, abrir muitas abas para o mesmo site pode esgotar esse orçamento. Isso é muito menos preocupante sobre HTTP/2, onde muitos fluxos são multiplexados em uma única conexão.
SSE vs. WebSockets
O SSE e os WebSockets entregam dados em tempo real, mas resolvem problemas diferentes.
| Server-Sent Events | WebSockets | |
|---|---|---|
| Direção | Unidirecional (servidor → cliente) | Bidirecional (full duplex) |
| Protocolo | HTTP simples | Upgrade ws:// / wss:// |
| Dados | Apenas texto (UTF-8) | Texto e binário |
| Reconexão | Automática, integrada | Implemente você mesmo |
| Ideal para | Feeds, notificações, progresso | Chat, jogos, edição colaborativa |
Use SSE quando o navegador só precisa ouvir — notificações, feeds ao vivo, painéis, atualizações de progresso. Sua reconexão automática, transporte HTTP simples e API cliente enxuta fazem dele a ferramenta mais simples para o trabalho. Use WebSockets quando o cliente também precisa enviar com frequência, quando você precisa de dados binários ou quando a latência por mensagem importa, como em aplicativos de chat e jogos multiplayer.
Se seus dados já estão em um fluxo assíncrono baseado em promessas, combinar os handlers do SSE com async/await para quaisquer requisições de acompanhamento (por exemplo, buscar detalhes completos quando uma notificação leve chega) mantém seu código limpo e legível.