Server-Sent Events
This lesson explains Server-Sent Events with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.
HTML5 Server-Sent Events API Overview
The HTML5 Server-Sent Events API allows a server to push real-time updates
to the browser over a single long-lived HTTP connection. It uses the
EventSource interface on the client and a
text/event-stream response from the server.
Server-Sent Events are commonly used for live notifications, dashboards,
stock price updates, sports scores, news feeds, progress updates, chat
message notifications, activity streams, monitoring tools, and any feature
that needs server-to-browser updates without full WebSocket complexity.
| Feature | Description |
| API Name | Server-Sent Events API |
| Main Client Object | EventSource |
| Data Direction | Server to browser only |
| Content Type | text/event-stream |
| Connection Type | Long-lived HTTP connection |
| Best Use | Live updates, notifications, progress, feeds, dashboards. |
Basic EventSource Client Example
// HTML5 Server-Sent Events API
const events =
new EventSource("/events");
events.onmessage = (event) => {
console.log(
"Message from server:",
event.data
);
};
events.onerror = () => {
console.log(
"SSE connection error or reconnecting."
);
};
The browser opens a persistent connection to /events. Every
message sent by the server is received through the
message event.
Basic Server Response Format
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: Hello from server
data: Another update
Each SSE message uses lines such as data:,
event:, id:, and retry:. A blank
line ends one event message.
Node.js SSE Server Example
// Express-style Server-Sent Events endpoint
app.get("/events", (request, response) => {
response.setHeader(
"Content-Type",
"text/event-stream"
);
response.setHeader(
"Cache-Control",
"no-cache"
);
response.setHeader(
"Connection",
"keep-alive"
);
const timer =
setInterval(() => {
const payload =
JSON.stringify(
{
time: new Date().toISOString()
}
);
response.write(
"data: " + payload + "\n\n"
);
}, 1000);
request.on("close", () => {
clearInterval(timer);
response.end();
});
});
This example sends a JSON timestamp every second. The server keeps the
connection open until the client disconnects.
How Server-Sent Events Work
| Step | Description | Example Syntax |
| Create Connection | Browser opens an SSE connection. | new EventSource("/events") |
| Server Holds Response | Server keeps HTTP response open. | text/event-stream |
| Send Updates | Server writes event-stream messages. | data: message\n\n |
| Receive Updates | Browser receives messages automatically. | events.onmessage |
| Reconnect | Browser reconnects if connection drops. | retry: 5000 |
| Close Connection | Client can stop listening. | events.close() |
EventSource Events
| Event | When It Runs | Common Use |
open | SSE connection opens successfully. | Show connected status. |
message | Default server message is received. | Read general updates. |
error | Connection error or reconnect attempt occurs. | Show reconnecting or fallback state. |
| Custom Events | Named server event is received. | Route notifications, alerts, and progress events. |
Named Events Example
event: notification
data: You have a new message
event: progress
data: 75
const events =
new EventSource("/events");
events.addEventListener(
"notification",
(event) => {
console.log(
"Notification:",
event.data
);
}
);
events.addEventListener(
"progress",
(event) => {
console.log(
"Progress:",
event.data + "%"
);
}
);
Named events help separate different types of server updates, making
client-side handling cleaner and easier to maintain.
Send and Receive JSON Data
event: order-update
data: {"orderId":123,"status":"shipped"}
events.addEventListener(
"order-update",
(event) => {
const data =
JSON.parse(event.data);
console.log(
data.orderId,
data.status
);
}
);
Server-Sent Events send text data, so structured data should be serialized
as JSON and parsed on the client.
Event Stream Fields
| Field | Description | Example |
data | Message data sent to the browser. | data: Hello |
event | Custom event name. | event: notification |
id | Message ID used for reconnect recovery. | id: 42 |
retry | Reconnect delay in milliseconds. | retry: 5000 |
| Comment | Lines starting with colon can keep connection alive. | : heartbeat |
Reconnect with Last Event ID
id: 101
event: notification
data: New alert
retry: 3000
When the SSE connection reconnects, the browser can send the last received
event ID using the Last-Event-ID header. This helps the
server resume missed messages when supported by the backend.
Close EventSource Connection
const events =
new EventSource("/events");
document
.querySelector("#stop")
.addEventListener("click", () => {
events.close();
console.log(
"SSE connection closed."
);
});
Use close() when updates are no longer needed, such as when
users leave a dashboard or disable live updates.
EventSource Ready State
console.log(
events.readyState
);
| Value | Constant | Meaning |
| 0 | CONNECTING | Connection is opening or reconnecting. |
| 1 | OPEN | Connection is open and receiving events. |
| 2 | CLOSED | Connection has been closed. |
Send Cookies with EventSource
const events =
new EventSource(
"/events",
{
withCredentials: true
}
);
Use withCredentials when your SSE endpoint requires cookies
or authentication credentials. Configure CORS carefully if the endpoint is
cross-origin.
Keep Connection Alive with Heartbeats
: heartbeat
: another heartbeat
Some servers and proxies close idle connections. Sending comment lines as
heartbeats can help keep long-lived SSE connections active.
File Processing Progress Example
const progressEvents =
new EventSource("/upload-progress");
progressEvents.addEventListener(
"progress",
(event) => {
const progress =
Number(event.data);
document.querySelector("#progress").style.width =
progress + "%";
document
.querySelector("#progress")
.setAttribute(
"aria-valuenow",
progress
);
if (progress === 100) {
progressEvents.close();
}
}
);
SSE is useful for long-running backend jobs such as imports, exports,
video processing, file scanning, and report generation.
Feature Detection
if ("EventSource" in window) {
console.log("Server-Sent Events supported.");
} else {
console.log("Use polling or WebSocket fallback.");
}
Always provide fallback behavior when real-time updates are important for
users on unsupported browsers or restricted environments.
Server-Sent Events vs WebSocket
| Feature | Server-Sent Events | WebSocket |
| Data Direction | Server to browser only. | Two-way communication. |
| Protocol | HTTP. | WebSocket protocol. |
| Automatic Reconnect | Built in. | Manual reconnect logic needed. |
| Best For | Notifications, feeds, progress, live dashboards. | Chat, multiplayer games, collaboration, trading apps. |
| Complexity | Simpler. | More flexible but more complex. |
Common Server-Sent Events Use Cases
- Live notification feeds.
- Dashboard metrics.
- News and activity streams.
- File upload or processing progress.
- Build and deployment logs.
- Stock, crypto, and pricing updates.
- Order status and delivery updates.
- Monitoring and alerting tools.
Server-Sent Events Best Practices
- Use
text/event-stream as the response content type. - Disable response buffering when using proxies or reverse proxies.
- Send heartbeat comments to keep idle connections alive.
- Use event IDs when clients need reconnect recovery.
- Close EventSource connections when updates are no longer needed.
- Send small JSON messages instead of large payloads.
- Use WebSockets when two-way real-time communication is required.
- Handle authentication, CORS, and rate limits carefully.
Security and Privacy Considerations
- Authenticate SSE endpoints when updates are user-specific.
- Do not stream private data to unauthorized clients.
- Use HTTPS for production event streams.
- Validate user access before sending each event.
- Be careful with cross-origin event streams and credentials.
- Avoid sending secrets or sensitive tokens in event data.
- Rate-limit high-frequency streams to protect server resources.
- Clean up server timers and listeners when clients disconnect.
Common Server-Sent Events Mistakes
- Using SSE for two-way communication when WebSocket is more appropriate.
- Forgetting the blank line after each event message.
- Sending the wrong
Content-Type. - Not disabling proxy buffering for event streams.
- Not cleaning up intervals after client disconnects.
- Streaming sensitive data without authentication.
- Keeping unnecessary connections open forever.
- Sending large payloads too frequently.
Key Takeaways
- Server-Sent Events send real-time updates from server to browser.
- Use
new EventSource("/events") on the client. - Use
text/event-stream on the server. - SSE supports automatic reconnect and message IDs.
- Use named events to organize different update types.
- Use WebSockets when you need full two-way communication.
Pro Tip
Use Server-Sent Events when your app mostly needs server-to-client updates
such as notifications, progress, logs, and dashboards. It is simpler than
WebSockets, works over HTTP, and includes automatic reconnect behavior.