Server Sent Events
SSE vs WebSockets: Choosing the Right Tool
Both SSE (Server-Sent Events) and WebSockets enable real-time communication, but they solve different problems.
| Feature | SSE | WebSockets |
|---|---|---|
| Direction | Server to client only | Full-duplex (both ways) |
| Protocol | HTTP | WebSocket (ws://) |
| Reconnection | Automatic | Manual |
| Browser support | All modern browsers | All modern browsers |
| Complexity | Simple | More complex |
| Firewall friendliness | High (uses HTTP/HTTPS) | Lower (separate protocol) |
Use SSE when: the server needs to push updates to the client but the client does not need to respond in the same channel. Examples: live notifications, upload progress, live score updates, server log streaming, dashboard metrics.
Use WebSockets when: you need bidirectional communication. Examples: chat, multiplayer games, collaborative editing.
How SSE Works
The client opens a standard HTTP request to a special endpoint. Instead of returning a normal response body and closing, the server keeps the connection open and writes events one at a time in a specific text format.
The SSE event format is plain text:
data: {"type":"update","value":42}
data: {"type":"update","value":43}
event: notification
data: {"message":"New order received"}
id: 100
event: progress
data: {"percent":75}
Each event ends with a blank line. The data: field is required. event:, id:, and retry: are optional.
Server-Side: Express SSE Endpoint
const express = require('express');
const app = express();
app.get('/events', (req, res) => {
// Required headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Optional: allow cross-origin requests
res.setHeader('Access-Control-Allow-Origin', '*');
// Send a comment every 30 seconds to keep the connection alive
const heartbeat = setInterval(() => {
res.write(': heartbeat\n\n');
}, 30000);
// Send an event immediately on connection
res.write('data: ' + JSON.stringify({ message: 'Connected to event stream' }) + '\n\n');
// Clean up when client disconnects
req.on('close', () => {
clearInterval(heartbeat);
console.log('Client disconnected from SSE');
});
});
app.listen(5000);
Pushing Live Data
A common pattern is a shared event emitter that your business logic writes to:
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Store active SSE connections
const clients = new Set();
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
clients.add(res);
res.write('data: ' + JSON.stringify({ connected: true }) + '\n\n');
req.on('close', () => clients.delete(res));
});
// Helper: broadcast to all connected SSE clients
function broadcast(event, data) {
const payload = 'event: ' + event + '\ndata: ' + JSON.stringify(data) + '\n\n';
clients.forEach(res => res.write(payload));
}
// When a new order arrives (called from your POST /orders handler):
app.post('/orders', express.json(), async (req, res) => {
const order = await Order.create(req.body);
broadcast('new-order', { id: order._id, total: order.total });
res.status(201).json({ success: true, data: order });
});
Client-Side: EventSource API
The browser's built-in EventSource API handles SSE connections, including automatic reconnection:
const eventSource = new EventSource('http://localhost:5000/events');
// Default message handler (for events without an explicit event: field)
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
// Handle named events
eventSource.addEventListener('new-order', (event) => {
const order = JSON.parse(event.data);
updateOrderDashboard(order);
});
eventSource.addEventListener('progress', (event) => {
const { percent } = JSON.parse(event.data);
updateProgressBar(percent);
});
// Handle errors and reconnection
eventSource.onerror = (error) => {
console.error('SSE error:', error);
// EventSource automatically attempts to reconnect
};
// Close the connection when done
function stopListening() {
eventSource.close();
}
The browser automatically reconnects if the connection drops, using the last received id: so the server can resume from the right point.
Progress Updates: Real-World Use Case
SSE is ideal for reporting the progress of a long-running server task (file processing, CSV import, sending bulk emails):
app.get('/import-progress', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const sendProgress = (percent, status) => {
res.write('event: progress\ndata: ' + JSON.stringify({ percent, status }) + '\n\n');
};
// Simulate long-running import
let percent = 0;
const interval = setInterval(() => {
percent += 10;
sendProgress(percent, percent < 100 ? 'Processing...' : 'Done!');
if (percent >= 100) {
clearInterval(interval);
res.end();
}
}, 500);
req.on('close', () => clearInterval(interval));
});
Practice Exercise
- Create an Express SSE endpoint at
GET /api/v1/notifications. - Store each connected response in a
Setand remove it when the client disconnects. - Create a
POST /api/v1/broadcastendpoint that sends a message to all connected SSE clients. - On the client, use
EventSourceto subscribe and display incoming notifications in a list. - Add a heartbeat comment every 20 seconds to prevent proxy timeouts.
Try it yourself
Key Takeaways
- SSE (Server-Sent Events) is a one-way protocol: the server pushes data to the client over a persistent HTTP connection.
- Set Content-Type to text/event-stream and write events in the format 'data: ...\n\n' to keep the connection alive.
- The browser's EventSource API handles automatic reconnection and supports named events with addEventListener.
- Store each active SSE response in a Set and remove it on request close to broadcast to all connected clients.
- Use SSE for notifications, progress bars, and live dashboards; use WebSockets when the client must also send data.
Quick Quiz
1.Which Content-Type header must an SSE (Server-Sent Events) endpoint return?
2.What happens when an SSE connection drops unexpectedly?
3.Why is SSE often preferred over WebSockets for live dashboards and notification systems?
4.What is the purpose of the heartbeat comment sent every 30 seconds in an SSE endpoint?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx