Server-Sent Events (SSE) — Streaming Guide
Advertisement
Server-Sent Events is a simpler alternative to WebSockets for server-to-client communication. Perfect when you only need data flowing one direction.
SSE vs WebSockets
SSE: Simpler, one-way, HTTP-based, built-in reconnection WebSockets: Bidirectional, persistent connection, more complex
Basic SSE Server
import express from "express";
const app = express();
// Keep track of connections
const clients: express.Response[] = [];
app.get("/events", (req, res) => {
// Set SSE headers
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
// Add to clients list
clients.push(res);
// Send a message when client connects
res.write("data: Connected to server\n\n");
// Handle client disconnect
req.on("close", () => {
const index = clients.indexOf(res);
if (index > -1) {
clients.splice(index, 1);
}
});
});
// Broadcast to all clients
app.post("/broadcast", (req, res) => {
const message = req.body.message;
clients.forEach((client) => {
client.write(`data: ${message}\n\n`);
});
res.json({ sent: clients.length });
});
app.listen(3000, () => {
console.log("SSE server on port 3000");
});
SSE Message Format
data: message content
event: event-name
id: 1
retry: 1000
data: {"key": "value"}
:this is a comment
data: line 1
data: line 2
- SSE vs WebSockets
- Basic SSE Server
- SSE Message Format
- Advanced SSE Server
- Client-Side Usage
- HTML5 Example
- Real-World Examples
- Live Notifications
- Live Analytics
- Error Handling and Reconnection
- FAQ
Advanced SSE Server
interface ClientEvent {
id: number;
name: string;
data: unknown;
}
let eventId = 0;
app.get("/events", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const clientId = Date.now();
// Send initial data
const events: ClientEvent[] = [
{ id: ++eventId, name: "user-online", data: { clientId } },
{ id: ++eventId, name: "user-count", data: { count: clients.length } },
];
events.forEach((event) => {
res.write(`id: ${event.id}\n`);
res.write(`event: ${event.name}\n`);
res.write(`data: ${JSON.stringify(event.data)}\n\n`);
});
clients.push(res);
// Heartbeat to keep connection alive
const heartbeat = setInterval(() => {
res.write(": heartbeat\n\n");
}, 30000);
req.on("close", () => {
clearInterval(heartbeat);
const index = clients.indexOf(res);
if (index > -1) {
clients.splice(index, 1);
}
});
});
// Send specific event types
function broadcast(eventName: string, data: unknown) {
const message = `id: ${++eventId}\nevent: ${eventName}\ndata: ${JSON.stringify(
data
)}\n\n`;
clients.forEach((client) => {
client.write(message);
});
}
// Usage
app.post("/notify", (req, res) => {
broadcast("notification", {
title: req.body.title,
message: req.body.message,
});
res.json({ ok: true });
});
Client-Side Usage
// Basic usage
const eventSource = new EventSource("http://localhost:3000/events");
eventSource.onmessage = (event) => {
console.log("Message:", event.data);
};
eventSource.addEventListener("notification", (event) => {
const data = JSON.parse(event.data);
console.log("Notification:", data);
});
eventSource.onerror = (error) => {
console.error("Connection error:", error);
eventSource.close();
};
// Close connection
eventSource.close();
HTML5 Example
<!DOCTYPE html>
<html>
<head>
<title>SSE Demo</title>
</head>
<body>
<h1>Server Messages</h1>
<div id="messages"></div>
<script>
const messagesDiv = document.getElementById("messages");
const eventSource = new EventSource("/events");
eventSource.addEventListener("user-count", (event) => {
const data = JSON.parse(event.data);
const el = document.createElement("p");
el.textContent = `Online users: ${data.count}`;
messagesDiv.appendChild(el);
});
eventSource.addEventListener("notification", (event) => {
const data = JSON.parse(event.data);
const el = document.createElement("div");
el.style.padding = "10px";
el.style.backgroundColor = "#ffd700";
el.innerHTML = ``;
messagesDiv.appendChild(el);
});
eventSource.onerror = () => {
messagesDiv.innerHTML += "<p>Connection lost</p>";
};
</script>
</body>
</html>
Real-World Examples
Live Notifications
// Backend
const notifications = new Map<string, express.Response[]>();
app.get("/events/:userId", (req, res) => {
const { userId } = req.params;
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
if (!notifications.has(userId)) {
notifications.set(userId, []);
}
notifications.get(userId)!.push(res);
res.write("data: Connected\n\n");
req.on("close", () => {
const clients = notifications.get(userId)!;
const index = clients.indexOf(res);
if (index > -1) clients.splice(index, 1);
});
});
app.post("/notify/:userId", (req, res) => {
const { userId } = req.params;
const clients = notifications.get(userId);
if (clients) {
clients.forEach((client) => {
client.write(
`data: ${JSON.stringify(req.body)}\n\n`
);
});
}
res.json({ ok: true });
});
// Frontend
const userId = "user-123";
const eventSource = new EventSource(`/events/${userId}`);
eventSource.onmessage = (event) => {
const notification = JSON.parse(event.data);
showNotification(notification);
};
Live Analytics
// Backend - send updates every second
app.get("/analytics", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
const interval = setInterval(() => {
const stats = {
activeUsers: Math.floor(Math.random() * 1000),
requests: Math.floor(Math.random() * 10000),
uptime: process.uptime(),
};
res.write(`data: ${JSON.stringify(stats)}\n\n`);
}, 1000);
req.on("close", () => {
clearInterval(interval);
});
});
// Frontend
const eventSource = new EventSource("/analytics");
eventSource.onmessage = (event) => {
const stats = JSON.parse(event.data);
updateDashboard(stats);
};
Error Handling and Reconnection
// Automatic reconnection with exponential backoff
let retryCount = 0;
const maxRetries = 5;
const retryDelay = 1000;
function connectSSE() {
const eventSource = new EventSource("/events");
eventSource.onopen = () => {
retryCount = 0;
console.log("Connected to server");
};
eventSource.onerror = () => {
eventSource.close();
if (retryCount < maxRetries) {
const delay = retryDelay * Math.pow(2, retryCount);
console.log(`Reconnecting in ${delay}ms`);
setTimeout(connectSSE, delay);
retryCount++;
}
};
}
connectSSE();
FAQ
Q: When should I use SSE instead of WebSockets? A: When you only need server→client communication and simpler code is preferred. SSE is easier but WebSockets are more flexible.
Q: How many concurrent SSE connections can Node.js handle? A: Thousands to tens of thousands depending on resources. Each connection is lightweight.
Q: Can SSE work through proxies? A: Yes, it uses standard HTTP. Works through any HTTP proxy.
Server-Sent Events are perfect for real-time features where the server pushes data to clients. Simpler than WebSockets and sufficient for many use cases.
Advertisement