Skip to content
Learni
View all tutorials
Développement Web

How to Implement Server-Sent Events in 2026

Lire en français

Introduction

Server-Sent Events (SSE) is a native web standard for unidirectional real-time communication from server to client. Unlike bidirectional WebSockets, SSE is simpler to implement: no complex frame management, automatic reconnection, and native support in all modern browsers.

Why use it in 2026? For live notifications (new messages, alerts), dashboard updates (real-time stats), or tracking long-running tasks without page reloads. It's lightweight (plain text over HTTP), scalable (persistent connections handled by the browser), and avoids costly polling.

This beginner tutorial guides you step by step: from a basic Node.js server to a robust client with reconnection. By the end, you'll have a fully functional, copy-pasteable example for custom events and JSON data. Estimated time: 15 minutes.

Prerequisites

  • Node.js 20+ installed
  • Basic JavaScript knowledge (variables, functions, async)
  • A code editor (VS Code recommended)
  • Modern browser (Chrome, Firefox)

Initialize the project and install dependencies

terminal
mkdir sse-tutorial
cd sse-tutorial
npm init -y
npm install express cors

This script creates a project folder, initializes package.json, and installs Express for the HTTP server plus CORS to allow cross-origin requests from the client. Run it in your terminal for a ready setup in 30 seconds.

Understanding SSE Basics

SSE uses a text/event-stream stream over a persistent HTTP connection. The server sends messages in the format data: message\n\n. The client, via EventSource, listens and parses automatically. Key advantage: automatic reconnection after 3 seconds if disconnected.

Create a Basic SSE Server

server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });

  let counter = 0;
  const interval = setInterval(() => {
    counter++;
    res.write(`data: Compteur: ${counter}\n\n`);
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000, () => {
  console.log('Serveur SSE sur http://localhost:3000');
});

This server exposes /events as an SSE endpoint. It sends an incrementing counter every second. The required headers enable the persistent stream; req.on('close') cleans up the interval to prevent memory leaks. Run with node server.js.

Create a Simple HTML Client

index.html
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>SSE Client</title>
</head>
<body>
  <h1>Server-Sent Events Demo</h1>
  <div id="messages"></div>

  <script>
    const eventSource = new EventSource('http://localhost:3000/events');
    const messagesDiv = document.getElementById('messages');

    eventSource.onmessage = (event) => {
      const newMessage = document.createElement('p');
      newMessage.textContent = event.data;
      messagesDiv.appendChild(newMessage);
    };

    eventSource.onerror = (error) => {
      console.error('Erreur SSE:', error);
    };
  </script>
</body>
</html>

This standalone HTML file connects to /events via EventSource. onmessage displays each received data. onerror logs errors (browser handles auto-reconnection). Open it in a browser to see the live counter.

Test the Basic Demo

  1. Run node server.js.
  2. Open index.html in your browser.
  3. Watch messages arrive every second. Close/reopen the tab: auto-reconnection!
Analogy: like a one-way radio stream where the server DJ broadcasts nonstop.

Add Custom Events

server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });

  let counter = 0;
  const interval = setInterval(() => {
    counter++;
    if (counter % 5 === 0) {
      res.write(`event: alerte\ndata: Alerte niveau ${counter}!\n\n`);
    } else {
      res.write(`data: Compteur normal: ${counter}\n\n`);
    }
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000, () => {
  console.log('Serveur SSE sur http://localhost:3000');
});

Upgrade: uses event: name to type messages (e.g., 'alerte'). Every 5 ticks, a special event is sent. This enables client-side filtering. Replace the old server.js and restart.

Advanced Client with Event Handling and JSON

index.html
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8">
  <title>SSE Client Avancé</title>
</head>
<body>
  <h1>SSE avec Événements Personnalisés</h1>
  <div id="normal"></div>
  <div id="alertes"></div>

  <script>
    const eventSource = new EventSource('http://localhost:3000/events');
    const normalDiv = document.getElementById('normal');
    const alertesDiv = document.getElementById('alertes');

    eventSource.addEventListener('alerte', (event) => {
      const data = JSON.parse(event.data);
      const alertDiv = document.createElement('div');
      alertDiv.style.color = 'red';
      alertDiv.textContent = `ALERTE: ${data.message}`;
      alertesDiv.appendChild(alertDiv);
    });

    eventSource.onmessage = (event) => {
      const p = document.createElement('p');
      p.textContent = event.data;
      normalDiv.appendChild(p);
    };

    eventSource.onerror = () => {
      console.log('Reconnexion en cours...');
    };
  </script>
</body>
</html>

Client filters events via addEventListener('alerte'). Parses JSON for structured data. onerror gracefully handles disconnections. Replace index.html; now alerts appear in red!

Server with Dynamic JSON Data

server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

let usersOnline = 0;

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });

  const interval = setInterval(() => {
    usersOnline += Math.floor(Math.random() * 3) - 1;
    if (usersOnline < 0) usersOnline = 0;

    const data = { users: usersOnline, timestamp: Date.now() };

    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }, 2000);

  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
});

app.listen(3000, () => {
  console.log('Serveur SSE JSON sur http://localhost:3000');
});

Now sends simulated JSON objects (online users). Perfect for real dashboards. The previous client parses them automatically. This shows complex data without changing the SSE protocol.

Best Practices

  • Always clean up intervals/timers on req.close to avoid leaks.
  • Limit reconnections: new EventSource(url, { heartbeatTimeout: 30000 }).
  • Use IDs for resume: id: ${timestamp}\n on server, auto-handled by client.
  • Buffer small messages to reduce network latency.
  • Scale with Redis for shared state across instances.

Common Errors to Avoid

  • Forgetting 'Connection: keep-alive': connection closes after one message.
  • Not handling CORS: cross-origin errors block the client.
  • Sending without final \n\n: EventSource can't parse messages.
  • Ignoring multiple clients: use a broadcaster like ioredis to scale.

Next Steps