Introduction
We've all built those apps where fresh data matters—chat applications, notification systems, or those fancy dashboards that marketing loves. While WebSockets often steal the spotlight for real-time features, I've found that when you only need one-way updates (server → client), SSE can be your secret weapon. In this blog, I will discuss all about SSE and when to use it with the implementation details.
What is SSE?
Think of Server-Sent Events as a subscription service where your server pushes updates to browsers over a standard HTTP connection. Unlike WebSockets, which handle two-way communication, SSE is a one-way street—from server to client.
What makes SSE stand out?
- It's built right into browsers (no extra libraries needed!)
- It's lighter than WebSockets when you just need simple updates
- It handles reconnections automatically if the connection drops
- Perfect for news feeds, notifications, stock tickers, and similar features
How It Actually Works
The process is beautifully simple:
- Your client (browser) opens a special connection to the server using the EventSource API
- The server responds with a
text/event-stream
content type and keeps the connection open - Whenever there's new data, the server sends it along, and the client automatically receives it
It's like having a newspaper delivered to your door whenever there's breaking news—no need to keep checking the newsstand!
Let's Build Something Real
Let's create a simple example where the server sends timestamps to the client every few seconds.
Backend Server (Using Express)
const express = require('express');
const app = express();
const PORT = 8000;
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const sendEvent = () => {
const data = `data: ${new Date().toLocaleTimeString()}\n\n`;
res.write(data);
};
const intervalId = setInterval(sendEvent, 3000);
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
Frontend Client (Using React)
import { useEffect, useState } from "react";
const App = () => {
const [messages, setMessages] = useState([]);
useEffect(() => {
const eventSource = new EventSource("http://localhost:8000/events");
eventSource.onmessage = (event) => {
setMessages(prevMessages => [...prevMessages, event.data]);
};
eventSource.onerror = (error) => {
console.error("EventSource failed:", error);
eventSource.close();
};
return () => {
eventSource.close();
};
}, []);
return (
<div>
<h1>Server-Sent Events Demo</h1>
<ul>
{messages.map((msg, index) => (
<li key={index}>{msg}</li>
))}
</ul>
</div>
);
};
export default App;
That's it! No polling, no complex setups—just real-time updates flowing into your React app.
Advantages and Disadvantages of Server Sent Events
Advantages of SSE
- It's ridiculously easy to implement
- Low overhead with just a plain HTTP connection
- Built-in reconnection support
- Perfect for those "notification bell" features and data feeds
- No need for complex WebSocket servers or libraries
Disadvantages of SSE
- It's one-way only (server to client)
- Some older browsers might give you headaches (but modern ones work great)
- Not ideal when you have many concurrent connections without HTTP/2
- Doesn't handle binary data natively (text only)
Custom Event Types
Here's where SSE gets even more interesting. You can define custom event types and handle them separately in your app!
Sending Custom Events from Server
const sendCustomEvent = () => {
res.write(`event: customEvent\n`);
res.write(`data: ${JSON.stringify({ message: "This is a custom event!" })}\n\n`);
};
Handling Them in Client Side
useEffect(() => {
const eventSource = new EventSource("http://localhost:8000/events");
eventSource.addEventListener("customEvent", (event) => {
const data = JSON.parse(event.data);
console.log("Custom event received:", data.message);
});
eventSource.onerror = (error) => {
console.error("EventSource failed:", error);
eventSource.close();
};
return () => {
eventSource.close();
};
}, []);
Now you can have distinct event channels, similar to WebSockets but with much less setup!
Securing Your SSE Connection
"But what about security?" I hear you ask. The EventSource API doesn't let you set headers directly (like Authorization headers), but there are workarounds:
Option 1: Query String Tokens
const token = "your_jwt_token_here";
const eventSource = new EventSource(`http://localhost:5000/events?token=${token}`);
Then on your server:
app.get('/events', (req, res) => {
const token = req.query.token;
if (!isValidToken(token)) {
res.status(401).end();
return;
}
// Continue sending events...
});
Just remember to use HTTPS to protect those tokens!
Option 2: Cookie-Based Authentication
If your users are already authenticated via cookies, you're in luck—browsers automatically send cookies with EventSource requests.
When Should You Actually Use SSE?
SSE shines when:
- You only need server-to-client updates
- You want a simple solution without the WebSocket overhead
- You're building notification systems, news feeds, score trackers, or IoT dashboards
But consider alternatives when:
- You need two-way communication
- Binary data transfer is a must-have
Conclusion
Server-Sent Events might be the unsung hero of real-time web development. They're simpler than WebSockets, built right into browsers, and perfect for many common use cases. Next time you reach for WebSockets just to send updates from your server, pause and ask yourself: "Could SSE handle this with less complexity?" More often than you might think, the answer will be yes! If you like this blog and want to learn more about Frontend Development and Software Engineering, you can follow me on Dev.to.