A vibrant, glowing digital heart radiating pulses of binary code, representing the real-time updates sent from the server to the client in a Server-Sent Event system.

Crafting Real-time Updates: Server-Sent Events with Node.js

Creating engaging and interactive applications often requires real-time communication between server and client. Server-Sent Events (SSE) provide a standard means for servers to push updates to clients. Let's explore how to leverage Node.js to implement SSE.

Setting the Stage: Building an HTTP Server

Our journey starts by establishing a basic HTTP server that will handle incoming requests.

import { createServer } from "node:http";

function requestHandler(req, res) {
  // Our code will go here
}

const server = createServer(requestHandler);

// set up listeners for `error` and `listening` events. These listeners handle server errors and log a message when the server starts:
server.on("error", (error) => {
  console.error("Server error:", error);
});
server.on("listening", () => {
  console.log("Server running at http://localhost:5000/");
});

// start the server on port 5000 (e.g. http://localhost:5000)
server.listen(5000);

Establishing the Rules: HTTP Headers for SSE

Having set the stage with our server, we now set the rules for communication. We define these within our request handler, specifying the correct HTTP headers for SSE.

res.writeHead(200, {
  "Content-Type": "text/event-stream",
  "Cache-Control": "no-cache",
  Connection: "keep-alive",
});

These headers ensure the client understands that it's receiving a server-sent event and keeps the connection open for real-time updates.

The Heartbeat: Sending Updates to the Client

With the stage set and the rules established, it's time to send our messages to the client.

let count = 0;
const intervalId = setInterval(() => {
  count++;
  res.write(`data: This is event number ${count}\n\n`);
}, 1000);

In the above code, we set an interval to send a new event to the client every second, creating a heartbeat of sorts.

Curtain Call: Graceful Disconnection

Not all good things last forever. At a certain point, we need to close the connection. In this case, we close the connection after sending five events.

if (count >= 5) {
  clearInterval(intervalId);
  res.write("event: close\ndata: \n\n");
  res.end();
}

This code also communicates to the client that it's time to stop listening for updates.

Planning for the Unexpected: Handling Client Disconnection

Life can be unpredictable, and so can clients. If a client disconnects prematurely, we should handle this gracefully to prevent potential server errors.

req.on("close", () => {
  clearInterval(intervalId);
  res.end();
});

The Final Act: Tying It All Together

Now, let's compile all the parts together into our complete script:

import { createServer } from "node:http";

function requestHandler(req, res) {
  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
    "access-control-allow-origin": "*",
  });

  let count = 0;
  const intervalId = setInterval(() => {
    count++;
    res.write(`data: This is event number ${count}\n\n`);
    if (count >= 5) {
      clearInterval(intervalId);
      res.write("event: close\ndata: \n\n");
      res.end();
    }
  }, 1000);

  req.on("close", () => {
    clearInterval(intervalId);
    res.end();
  });
}

const server = createServer(requestHandler);

server.listen(5000);

server.on("error", (error) => {
  console.error("Server error:", error);
});

server.on("listening", () => {
  console.log("Server running at http://localhost:5000/");
});

Congratulations on crafting your Server-Sent Events pipeline with Node.js! With this foundation, you're well-prepared to integrate real-time features into your web applications.