Usando Server-Sent Events

Ejemplo de uso de Server-Sent Events


En el artículo anterior explicamos las diferencias principales entre Server-Sent events y WebSockets. Vamos ahora a ver un ejemplo real de SSE en NodeJS, para entenderlo.

Estructura principal del ejemplo

Recordemos que el aspecto clave de Server-Sent Events es que la comunicación es unidireccional, por lo que sólo podemos comunicarnos de servidor a cliente.

La estructura principal de nuestro ejemplo sería el siguiente:

import { readFile } from "node:fs/promises";
import { createServer } from "node:http";

const PORT = 4321;

const server = createServer(async (req, res) => {

  const isClient = req.url === "/";
  const isServer = req.url === "/events";

  if (isServer) {
    // Código del servidor
  }
  else if (isClient) {
    // Código del cliente
  }
  else {
    res.writeHead(404, "Not found");
  }
});

server.listen(PORT, () => console.log(`Escuchando en http://localhost:${PORT}`));

Observa que hemos creado un servidor donde escuchamos dos rutas:

  • 1️⃣ /events es la ruta del servidor SSE.
  • 2️⃣ / es la ruta del cliente (navegador)
  • 3️⃣ En el caso de acceder a cualquier otra ruta, obtendremos un 404 error.

Veamos ahora el fragmento de código de cada parte, servidor y cliente.

El servidor

En el caso del servidor vamos a enviar una cabecera Content-Type con el valor text/event-stream. Esto nos indica que estamos enviando streams de datos, es decir, un grupo de información cada cierto tiempo:

if (isServer) {
  res.setHeader("Content-Type", "text/event-stream");
  res.writeHead(200);

  const timer = setInterval(() => {
    const data = { message: "Hello!", timestamp: new Date().toISOString() }
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }), 5000);

  req.on("close", () => clearInterval(timer));
}

Un poco más abajo creamos un temporizador setInterval() que cada 5 segundos enviará el contenido del objeto data a través del stream de datos del servidor.

El cliente

Ahora vamos con el fragmeto de código del cliente, donde simplemente leemos el contenido del archivo index.html y lo enviamos al navegador:

if (isClient) {
  const textContent = await readFile("./index.html", { encoding: "utf-8" });
  res.writeHead(200, textContent);
}

El contenido de dicho index.html sería el siguiente:

  • Creamos un HTML básico, donde mostramos un <h1> y un <div> contenedor vacío.
  • Un poco más abajo, le damos un estilo CSS básico.
<h1>Start</h1>
<div class="container"></div>

<style>
  body {
    background: indigo;
    color: white;
    font-size: 3rem;
  }
</style>

<script type="module">
  if (EventSource) {
    const source = new EventSource('/events');
    const container = document.querySelector(".container");

    source.addEventListener("message", ({ data }) => {
      const { message, timestamp } = JSON.parse(data);
      const element = document.createElement("div");
      element.textContent = `[${timestamp}] Message: ${message}`;
      container.append(element);
    });
  }
</script>

Vamos ahora al código Javascript. Tras comprobar si está soportado, creamos un nuevo objeto EventSource con la ruta a la que se conectará para obtener los datos. A continuación, simplemente escuchamos los eventos message y cada vez que se reciba uno, añadimos un nuevo elemento al DOM de la página HTML.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev