Skip to main content

Server-Sent Events

What It Is

Server-Sent Events, also called SSE, is a unidirectional communication protocol where the server pushes real-time updates to the client.

It uses a single long-lived HTTP connection.

SSE = Server continuously sends updates to client over one HTTP connection

Simple Meaning

SSE is like a live display board.

The client opens the connection once, then the server keeps sending updates.

Client -> Server: Open connection once
Server -> Client: Keep sending updates

The client does not need to repeatedly ask for new data.


How SSE Works

SSE follows a simple flow.

1. Client sends HTTP request using EventSource
2. Server responds with text/event-stream headers
3. Connection stays open
4. Server sends messages in SSE format
5. Browser listens and triggers events automatically

Unlike normal REST APIs, the response is not closed immediately.

The connection remains open for continuous updates.


Client Connection

The browser connects to an SSE endpoint using EventSource.

const eventSource = new EventSource("/sse");

eventSource.onmessage = (event) => {
console.log(event.data);
};

This opens a long-lived connection to the server.

Whenever the server sends a message, onmessage runs automatically.


Required Server Headers

The server must send special headers for SSE.

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

These headers tell the browser:

  • the response is an event stream
  • the response should not be cached
  • the connection should stay open

Flow Diagram

Client                                      Server
| |
| ---- Connect using EventSource ---------> |
| |
| <---- Headers: text/event-stream -------- |
| |
| <---- Event: data ----------------------- |
| |
| <---- Event: data ----------------------- |
| |
| <---- Event: data ----------------------- |
| |
| <----------- Connection stays open -------|

Message Format

SSE messages must follow a specific format.

data: Your message here\n\n

Example:

data: Hello\n\n

Another example:

data: Server Time: 10:30\n\n

Important rules:

  • message must start with data:
  • message must end with \n\n
  • without \n\n, the browser will not emit the event
data: message + blank line = browser receives event

Basic Server Example

This Express endpoint opens an SSE connection and sends server time every 5 seconds.

app.get("/sse", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");

res.write(`data: Connected\n\n`);

const intervalId = setInterval(() => {
res.write(`data: ${new Date().toLocaleTimeString()}\n\n`);
}, 5000);

req.on("close", () => {
clearInterval(intervalId);
res.end();
});
});

The close event is important because the server must clean up the interval when the client disconnects.


Data Types

SSE only sends text.

If you need to send JSON, convert it to a string on the server.

res.write(`data: ${JSON.stringify({ msg: "hello" })}\n\n`);

Then parse it on the client.

const data = JSON.parse(event.data);
Server sends text
Client parses text if needed

Custom Events

SSE supports custom event names.

The server can send an event type before sending data.

res.write(`event: notification\n`);
res.write(`data: New message\n\n`);

The client can listen to that specific event.

eventSource.addEventListener("notification", (event) => {
console.log(event.data);
});

Use custom events when different update types need separate handling.


Event Fields

SSE supports multiple fields.

FieldPurpose
dataActual payload
eventCustom event name
idEvent ID used for reconnection
retryReconnection delay

Example:

id: 1
event: update
data: Hello\n\n

Connection Behavior

SSE connection behavior is simple.

Persistent HTTP connection
Automatically reconnects on failure
Uses Last-Event-ID for resuming

This makes SSE useful for update streams where the server needs to keep pushing information to the browser.


Advantages

SSE is useful because it is simple and HTTP-based.

AdvantageMeaning
Simple to implementUses standard HTTP
Auto reconnectBrowser reconnects automatically on failure
LightweightSimpler than WebSocket for one-way updates
HTTP compatibleWorks over standard HTTP/1.1
Good for streamsUseful for continuous server updates

Limitations

SSE is not suitable for every real-time system.

LimitationMeaning
One-way onlyServer sends to client, not both ways
Text-only dataNo native binary data support
Connection limitBrowser may limit connections per domain
Not ideal for high frequencyBetter for lower-frequency updates
Browser support issuesOlder browsers may need polyfills

Real-World Challenges

SSE needs careful handling in production.

Common challenges:

  • sticky sessions for scaling
  • authentication handling
  • firewalls or proxies may block or buffer
  • backward compatibility issues
  • testing and debugging difficulty
  • resource cleanup for open connections
  • memory leaks from uncleared intervals
  • browser connection limits
  • load balancer idle timeouts
  • shared pub/sub may be needed for multiple servers
  • no native binary data support
SSE is simple, but long-lived connections still need cleanup and scaling planning.

SSE vs WebSocket

FeatureSSEWebSocket
DirectionOne-wayTwo-way
ProtocolHTTPWS
ComplexitySimpleMore complex
Best Use CaseNotifications, logs, dashboardsChat, gaming, interactive real-time apps

When to Use

SSE is a good fit for server-to-client updates.

Common use cases:

  • live dashboards
  • notifications
  • logs streaming
  • AI streaming responses
  • low-frequency stock updates
Use SSE when the server needs to continuously push updates to the client.

When Not to Use

Avoid SSE when the application needs frequent two-way communication.

Not ideal for:

  • chat apps
  • multiplayer apps
  • high-frequency real-time systems
If both client and server need to talk anytime, WebSocket is usually better.

Frontend Code

This example opens an SSE connection and displays the latest server message.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />

<title>SSE Demo</title>
</head>

<body>
<h1>SSE Demo</h1>

<div id="sse-data"></div>

<script>
const eventSource = new EventSource("/sse");
const container = document.getElementById("sse-data");

eventSource.onmessage = (event) => {
console.log(event.data);
container.innerText = event.data;
};

eventSource.onerror = () => {
console.log("Connection lost");
};
</script>
</body>
</html>

Backend Code

This Express server sends the current server time to the client every 5 seconds.

const express = require("express");
const { join } = require("node:path");

const app = express();

app.get("/", (req, res) => {
res.sendFile(join(__dirname, "index.html"));
});

app.get("/sse", (req, res) => {
// Required headers for SSE
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");

// Send initial event
res.write(`data: Connected to SSE\n\n`);

// Send data every 5 sec
const intervalId = setInterval(() => {
const time = new Date().toLocaleTimeString();
res.write(`data: Server Time: ${time}\n\n`);
}, 5000);

// Handle client disconnect
req.on("close", () => {
console.log("Client disconnected");
clearInterval(intervalId);
res.end();
});
});

const PORT = 3000;

app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});

What Happens in the Code

The browser creates an SSE connection.

const eventSource = new EventSource("/sse");

The server keeps the connection alive using SSE headers.

res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");

The server sends a message using SSE format.

res.write(`data: Connected to SSE\n\n`);

Then it sends updated time every 5 seconds.

res.write(`data: Server Time: ${time}\n\n`);

The browser receives each update using onmessage.

eventSource.onmessage = (event) => {
container.innerText = event.data;
};

How to Test

Install Express.

npm install express

Run the server.

node index.js

Open the app.

http://localhost:3000

Open the Network tab and click the /sse request.

You should see the request stay open and receive streamed updates from the server.


SSE Table

AreaExplanation
Full formServer-Sent Events
DirectionServer to client
ConnectionLong-lived HTTP connection
Client APIEventSource
Server headertext/event-stream
Data typeText
ReconnectAutomatic
Best fitNotifications, logs, dashboards, streaming responses

Basic Checklist

Create an SSE endpoint
Set Content-Type to text/event-stream
Set Cache-Control to no-cache
Set Connection to keep-alive
Use EventSource on the client
Send messages using data: message\n\n
Use JSON.stringify for JSON payloads
Use custom events when needed
Handle client disconnect with req.on("close")
Clear intervals and clean resources
Avoid SSE for two-way chat or multiplayer apps

Interview Style Answer

Server-Sent Events, or SSE, is a unidirectional communication technique where the server pushes real-time updates to the client over a single long-lived HTTP connection. The client opens the connection once using EventSource, and the server responds with Content-Type: text/event-stream. Messages must follow the SSE format, usually data: message\n\n, otherwise the browser will not emit the event. SSE supports automatic reconnection, custom events, event IDs, and retry delay. It is simpler than WebSocket and works well for notifications, live dashboards, log streaming, AI streaming responses, and low-frequency stock updates, but it is not suitable for chat, multiplayer apps, high-frequency systems, or cases needing two-way communication.


One-Line Summary

SSE = Server pushes text updates to the browser over one long-lived HTTP connection.

Final Mental Model

Client opens connection once
Server keeps connection open
Server sends data: messages
Browser receives events automatically

Best for one-way live updates.