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.
| Field | Purpose |
|---|---|
| data | Actual payload |
| event | Custom event name |
| id | Event ID used for reconnection |
| retry | Reconnection 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.
| Advantage | Meaning |
|---|---|
| Simple to implement | Uses standard HTTP |
| Auto reconnect | Browser reconnects automatically on failure |
| Lightweight | Simpler than WebSocket for one-way updates |
| HTTP compatible | Works over standard HTTP/1.1 |
| Good for streams | Useful for continuous server updates |
Limitations
SSE is not suitable for every real-time system.
| Limitation | Meaning |
|---|---|
| One-way only | Server sends to client, not both ways |
| Text-only data | No native binary data support |
| Connection limit | Browser may limit connections per domain |
| Not ideal for high frequency | Better for lower-frequency updates |
| Browser support issues | Older 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
| Feature | SSE | WebSocket |
|---|---|---|
| Direction | One-way | Two-way |
| Protocol | HTTP | WS |
| Complexity | Simple | More complex |
| Best Use Case | Notifications, logs, dashboards | Chat, 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
| Area | Explanation |
|---|---|
| Full form | Server-Sent Events |
| Direction | Server to client |
| Connection | Long-lived HTTP connection |
| Client API | EventSource |
| Server header | text/event-stream |
| Data type | Text |
| Reconnect | Automatic |
| Best fit | Notifications, 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.