Real-time features differentiate modern applications from static websites. Server-Sent Events provide an elegant solution for pushing updates from server to client. This tutorial walks through implementing SSE in your applications.
The Problem with Polling
Traditional web applications use polling. The client sends a request, gets a response, and then waits before asking again. This approach wastes resources and introduces delay.
Imagine tracking inventory changes in a pet store. With polling, your client checks every five seconds for updates. Between checks, a pet gets sold. The user sees outdated information until the next poll.
Polling also burdens your servers. Each request requires processing, even when no data changed. At scale, this creates unnecessary load.
SSE solves these problems by keeping a connection open. The server pushes updates the moment they occur. Users see changes instantly, and servers process fewer requests.
Setting Up the Client
The browser provides built-in support for SSE through the EventSource API. No external libraries needed.
Create an EventSource object pointing to your endpoint:
// For real-time streaming with the Pet Store API, use the chat completions endpoint with streaming
const response = await fetch('https://api.petstoreapi.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'petstore-ai',
messages: [{ role: 'user', content: 'List available pets' }],
stream: true
})
});
The browser automatically handles connection management. It opens the connection, handles reconnection on failures, and parses incoming messages.
Listen for messages using event listeners:
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
handleNotification(data);
};
The onmessage handler catches all messages without a specific event type.
For different message types, use named event handlers:
eventSource.addEventListener('order-created', (event) => {
const order = JSON.parse(event.data);
showNotification(`New order: #${order.id}`);
});
eventSource.addEventListener('pet-status-changed', (event) => {
const update = JSON.parse(event.data);
updatePetCard(update);
});
Named events keep your code organized. Different handlers process different update types.
Implementing Server-Side SSE
Your server needs to maintain persistent connections. The implementation depends on your server framework, but the principles remain consistent.
Set required headers for SSE responses:
function handleSSE(request, response) {
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
The Content-Type tells the browser this is an event stream. The no-cache header prevents intermediate servers from buffering. Keep-alive maintains the connection.
Send messages using the event stream format:
function sendEvent(response, eventType, data) {
response.write(`event: ${eventType}\n`);
response.write(`data: ${JSON.stringify(data)}\n\n`);
}
Each message needs a blank line to separate it from the next. The event type is optional but helps clients route messages.
Practical Implementation Example
Let's build a real-time pet store notification system. This example shows how to integrate SSE effectively.
First, create the server endpoint:
app.get('/api/events/pets', (req, res) => {
// Set SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send initial connection message
res.write('event: connected\n');
res.write(`data: ${JSON.stringify({ status: 'connected' })}\n\n`);
// Subscribe to pet updates
const subscriber = (update) => {
res.write(`event: pet-update\n`);
res.write(`data: ${JSON.stringify(update)}\n\n`);
};
petStore.on('update', subscriber);
// Clean up when connection closes
req.on('close', () => {
petStore.off('update', subscriber);
});
});
Now the client can connect and receive updates:
const petsEventSource = new EventSource('/api/events/pets');
petsEventSource.addEventListener('connected', (event) => {
console.log('Connected to pet updates');
});
petsEventSource.addEventListener('pet-update', (event) => {
const update = JSON.parse(event.data);
updatePetDisplay(update);
});
This pattern works for any real-time update system. Connect on page load, listen for events, update the UI.
Handling Reconnection
Browsers automatically reconnect when SSE connections drop. However, you should handle reconnection gracefully in your application.
Track connection state:
let isConnected = false;
petsEventSource.onopen = () => {
isConnected = true;
updateConnectionStatus('Connected');
};
petsEventSource.onerror = (error) => {
isConnected = false;
updateConnectionStatus('Reconnecting...');
};
Show users the current state. They understand when updates pause during reconnection.
Handle missed updates by refetching data after reconnection:
petsEventSource.addEventListener('connected', async (event) => {
// Fetch current state after reconnection
const currentPets = await fetch('/api/pets').then(r => r.json());
renderPets(currentPets);
});
This ensures users see accurate data even after connection issues.
Managing Multiple SSE Connections
Large applications might need multiple SSE streams. Different features require different update types.
Create separate connections for different concerns:
const ordersStream = new EventSource('/api/events/orders');
const inventoryStream = new EventSource('/api/events/inventory');
const notificationsStream = new EventSource('/api/events/notifications');
This separation keeps concerns isolated. Updates to one feature don't affect others.
Be aware of browser connection limits. Most browsers limit concurrent connections to six per domain. If you exceed this, combine related streams or use a single multiplexed stream.
Error Handling Best Practices
SSE connections can fail. Network issues, server restarts, and timeouts all cause disconnections. Handle these gracefully.
Always have a retry mechanism:
const eventSource = new EventSource('/api/events/data');
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
// EventSource handles automatic retry
// Add logging or user notification if needed
};
The EventSource retries automatically with exponential backoff. You can customize retry intervals server-side by sending a retry: message.
Implement fallback for critical applications:
async function fetchWithPollingFallback() {
// Try SSE first
if (typeof EventSource !== 'undefined') {
return setupSSE();
}
// Fallback to polling for older browsers
return setupPolling();
}
This ensures your application works everywhere.
Using with Pet Store API
The Pet Store API provides SSE endpoints ready for use. Connect to receive instant updates when pet data changes.
Check the documentation at docs.petstoreapi.com for available endpoints. Each endpoint streams specific event types. Subscribe to the events your application needs.
SSE provides the simplest path to real-time features. The server pushes, the client receives. No complex setup, no WebSocket overhead. Just live data when you need it.