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:

  1. Your client (browser) opens a special connection to the server using the EventSource API
  2. The server responds with a text/event-stream content type and keeps the connection open
  3. 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.