Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      May 31, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      May 31, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      May 31, 2025

      How To Prevent WordPress SQL Injection Attacks

      May 31, 2025

      How to install SteamOS on ROG Ally and Legion Go Windows gaming handhelds

      May 31, 2025

      Xbox Game Pass just had its strongest content quarter ever, but can we expect this level of quality forever?

      May 31, 2025

      Gaming on a dual-screen laptop? I tried it with Lenovo’s new Yoga Book 9i for 2025 — Here’s what happened

      May 31, 2025

      We got Markdown in Notepad before GTA VI

      May 31, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Oracle Fusion new Product Management Landing Page and AI (25B)

      May 31, 2025
      Recent

      Oracle Fusion new Product Management Landing Page and AI (25B)

      May 31, 2025

      Filament Is Now Running Natively on Mobile

      May 31, 2025

      How Remix is shaking things up

      May 30, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      How to install SteamOS on ROG Ally and Legion Go Windows gaming handhelds

      May 31, 2025
      Recent

      How to install SteamOS on ROG Ally and Legion Go Windows gaming handhelds

      May 31, 2025

      Xbox Game Pass just had its strongest content quarter ever, but can we expect this level of quality forever?

      May 31, 2025

      Gaming on a dual-screen laptop? I tried it with Lenovo’s new Yoga Book 9i for 2025 — Here’s what happened

      May 31, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Server-Sent Events vs WebSockets – How to Choose a Real-Time Data Exchange Protocol

    Server-Sent Events vs WebSockets – How to Choose a Real-Time Data Exchange Protocol

    January 3, 2025

    In our fast-paced digital era, real-time data exchange has become critical in creating responsive and dynamic user experiences. It’s especially important in applications like live news updates, chat systems, AI generative platforms, and so on.

    In this article, you’ll learn about WebSockets and Server-Sent Events (SSE), two powerful communication protocols that ensure seamless, real-time interactions in modern web applications.

    By examining their differences, advantages, and use cases, you’ll gain a clear understanding of how to choose the right protocol to optimize scalability and performance. This article also includes simple example implementations using Node.js, allowing you to see these technologies in action.

    To help you solidify your knowledge further, we’ll conclude with practical project recommendations, offering hands-on opportunities to apply what you’ve learned.

    Table Of Contents

    1. What is a WebSocket?

    2. Advantages and Disadvantages of WebSockets

    3. Use Cases for WebSockets

    4. How to Create a WebSocket Server with Node.js

    5. What are Server-Sent Events (SSE)?

    6. Advantages and Disadvantages of Server-Sent Events

    7. Use Cases for SSE

    8. How to Implement Server-Sent Events using Node.js

    9. WebSockets vs Server-Sent Events

    10. Which is Better: Server-Sent Events or WebSockets?

    11. Conclusion

    What is a WebSocket?

    In short, a WebSocket is a communication protocol that enables full-duplex, low-latency, event-driven connections between the server and the browser. In case you’re not familiar, full-duplex refers to the ability to send and receive data simultaneously between a client (like a web browser) and a server over a single connection.

    Unlike HTTP, which operates in a request-response model, WebSockets enable persistent and continuous data exchange. This means that the data is exchanged in real-time, and pulling it from the server is unnecessary each time.

    The WebSockets protocol was formalized in 2011 by the IETF through RFC 6455 and is now supported by all major browsers (Chrome, Edge, Safari, and so on).

    Although WebSockets differ from HTTP, both protocols operate on the OSI model’s Application Layer (layer 7) and rely on TCP/IP at the Transport Layer (layer 4). The OSI (Open Systems Interconnection) model is a conceptual framework used to understand network communication. It divides the network into 7 layers, each responsible for a specific function, from physical data transmission to application-level interactions.

    Similar to HTTP and HTTPS, WebSockets have a unique set of prefixes:

    • ws: indicates an unencrypted connection without TLS and should not be opened from HTTPS-secured sites.

    • wss: indicates an encrypted connection secured by TLS and shouldn’t be opened from HTTP (non-secure) sites.

    How Do WebSockets Work?

    As I mentioned earlier, WebSockets establish a persistent, bidirectional connection between the client and the server. The process begins with an HTTP handshake initiated by the client, where the client requests a WebSocket connection by sending a specific header to the server. If the server accepts the request, it responds with a status code 101 confirming the upgrade to a WebSocket connection.

    Once the connection is established, the WebSocket protocol takes over, and both the client and the server can send and receive data at any time without the need for repeated handshakes. This continuous connection allows real-time communication with minimal latency, as data is exchanged immediately without waiting for additional requests.

    The WebSocket connection remains open until either the client or server decides to close it. This ensures efficient and fast data exchange, making it ideal for real-time applications like chat systems, online gaming, or live data feeds.

    WebSocket full-duplex client-server connection

    Advantages of WebSockets

    • Full-duplex connection: both client and server can send and receive data simultaneously.

    • Low latency: since WebSockets maintain an open connection, they ensure minimal delay in data transfer by eliminating the overhead of repeatedly establishing and tearing down connections, ensuring minimal delay in data transfer.

    • Reduced bandwidth usage: unlike HTTP requests, which include headers for every request, WebSockets only require a single handshake, resulting in smaller data packets and reduced bandwidth consumption.

    • Cross-Platform Compatibility: as stated earlier, WebSockets are supported by most modern browsers and programming frameworks, which ensures broad applicability.

    Disadvantages of WebSockets

    • Complexity in implementation: WebSockets require a dedicated server and a special protocol.

    • Vulnerability to attacks: Without proper security (wss prefix) and authentication mechanisms, WebSockets are susceptible to cross-site WebSocket hijacking (CSWSH) and man-in-the-middle (MITM) attacks.

    • No built-in security: Unlike HTTP, WebSockets do not inherently support request-response headers for additional security. Thus, it’s necessary to implement token-based authentication or other secure methods manually.

    Use Cases for WebSockets

    WebSockets have revolutionized how applications deliver real-time communication. This protocol powers various industries by enabling low-latency, bidirectional data flow. Let’s talk about some good use cases for WebSockets:

    1. Chat Applications

    WebSockets’ full-duplex connection ensures that messages are delivered instantly and without interruptions, making them the perfect choice for real-time communication. This technology powers platforms like Slack, Discord, and various live customer support chat systems, providing seamless and efficient interactions.

    2. Online Gaming

    WebSockets are essential for fast-paced online games like Clash Royal, where real-time communication between players and servers is crucial. By maintaining a persistent, two-way connection, WebSockets allow immediate transmission of actions, such as moves or attacks, ensuring that all players experience seamless gameplay without lag.

    3. Real-Time Dashboards

    Tools like Datadog and e-commerce platforms use WebSockets to ensure system metrics, sales, and inventory data are always current, eliminating manual refreshes and enhancing user experience.

    WebSockets also excel at handling big data, streaming, and visualizing large volumes of information with low latency. This makes them the perfect choice for industries such as finance, healthcare, and logistics, where real-time insights are essential for effective decision-making.

    An example is DataTableDev, a grid prototype capable of working with massive data volumes, demonstrating WebSockets’ potential in real-time data processing.

    How to Create a WebSocket Server with Node.js

    Before setting up a simple WebSocket server with Node.js to handle secure connections, you’ll need a TLS certificate to ensure the communication is encrypted. You can acquire one from a trusted Certificate Authority (CA) like Let’s Encrypt or use a self-signed certificate for testing.

    Below is the complete implementation of a WebSocket Secure (WSS) server using Node.js:

    We’ll start on the server-side. Firstly, let’s import the required modules:

    const https = require('https');  // Module for creating an HTTPS server
    const fs = require('fs');        // Module to read files (used to load TLS certificates)
    const WebSocket = require('ws'); // WebSocket library to handle WebSocket connections
    

    Next, we’ll load TLS certificates for secure communication (wss://).

    const serverOptions = {
      cert: fs.readFileSync('cert.pem'), // Load the TLS certificate for HTTPS encryption
      key: fs.readFileSync('key.pem'),   // Load the private key associated with the certificate
    };
    

    serverOptions reads the TLS certificate and private key from files (cert.pem and key.pem) and holds them. These are essential for establishing secure communication using the wss:// protocol since they enable encryption for data transmitted between the server and the client.

    Since the WebSocket server runs on top of the HTTPS server, we first create and initialize the HTTPS server using the serverOptions, and then set up the WebSocket server.

    // Create the HTTPS server with the loaded certificates and initialize it with TLS options
    const httpsServer = https.createServer(serverOptions); 
    // Create a WebSocket server that runs on top of the HTTPS server
    const wss = new WebSocket.Server({ server: httpsServer });
    

    Now it’s time to define the behavior when a new WebSocket connection is established. You’ll need to handle incoming messages from the WebSocket client, send a response back, and manage the disconnection process. In this tutorial, we’ll keep it simple by printing the received data to the console.

    // Define the behavior when a new WebSocket connection is established
    wss.on('connection', (ws) => {
      console.log('Client connected');
    
      // Handle incoming messages from the WebSocket client
      ws.on('message', (message) => {
        console.log(`Received: ${message}`); 
        ws.send(`Server received: ${message}`); // Send a response back to the client
      });
    
      // Handle when a client disconnects
      ws.on('close', () => {
        console.log('Client disconnected');
    
      // Send an initial message to the client when the connection is established
      ws.send('Welcome to the secure WebSocket server!');
    });
    

    Last but not least, you need to define the port where the HTTPS WebSocket server will listen for incoming connections. In this example, we use a port 8080. After that, we start the HTTPS server and make it listen on the specified port. Once the server is up and running a log message will be printed to confirm that the secure WebSocket server is ready.

    // Define the port where the HTTPS WebSocket server will listen for incoming connections
    const PORT = 8080;
    
    // Start the HTTPS server and begin listening on the specified port
    httpsServer.listen(PORT, () => {
      console.log(`Secure WebSocket server running at wss://localhost:${PORT}`); // Log a message when the server starts
    });
    

    And that’s it for the server-side part. Your full code should look like this:

    // Import required modules
    const https = require('https');  // Module for creating an HTTPS server
    const fs = require('fs');        // Module to read files (used to load TLS certificates)
    const WebSocket = require('ws'); // WebSocket library to handle WebSocket connections
    
    // Load TLS certificates for secure communication (wss://)
    const serverOptions = {
      cert: fs.readFileSync('cert.pem'), // Load the TLS certificate for HTTPS encryption
      key: fs.readFileSync('key.pem'),   // Load the private key associated with the certificate
    };
    
    // Create the HTTPS server with the loaded certificates and initialize it with TLS options
    const httpsServer = https.createServer(serverOptions); 
    // Create a WebSocket server that runs on top of the HTTPS server
    const wss = new WebSocket.Server({ server: httpsServer }); 
    
    // Define the behavior when a new WebSocket connection is established
    wss.on('connection', (ws) => {
      console.log('Client connected'); 
    
      // Handle incoming messages from the WebSocket client
      ws.on('message', (message) => {
        console.log(`Received: ${message}`); 
        ws.send(`Server received: ${message}`); // Send a response back to the client
      });
    
      // Handle when a client disconnects
      ws.on('close', () => {
        console.log('Client disconnected'); 
      });
      // Send an initial message to the client when the connection is established
      ws.send('Welcome to the secure WebSocket server!');
    });
    
    // Define the port where the HTTPS WebSocket server will listen for incoming connections
    const PORT = 8080;
    
    // Start the HTTPS server and begin listening on the specified port
    httpsServer.listen(PORT, () => {
      console.log(`Secure WebSocket server running at wss://localhost:${PORT}`); // Log a message when the server starts
    });
    

    To run the created server with Node.js, type the following line into Command Prompt / Terminal:

    node wss-server.js

    Connect to the server using a WebSocket client or browser console at wss://localhost:8080.

    Once the connection is established, the client can send and receive messages. Now well look at a simple example of how to receive and send messages on the client side.

    To start, let’s define a basic HTML structure:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>WebSocket Client</title>
    </head>
    <body>
      <h1>WebSocket Client</h1>
      <div id="messages"></div>
      <input type="text" id="messageInput" placeholder="Type a message">
      <button onclick="sendMessage()">Send Message</button>
    
      <script>
        <!-- JS code goes here -->
      </script>
    </body>
    </html>
    

    The <button> element has an onclick event that triggers the sendMessage() function when clicked. Before we dive into the function, let’s first establish a WebSocket connection to the server. We’ll also define event listeners to handle the following:

    1. When the WebSocket connection is successfully established.

    2. When a message is received from the server.

    These event listeners will ensure that we can interact with the server and handle incoming data in real time.

        // Establish a WebSocket connection to the server
        const socket = new WebSocket('wss://localhost:8080'); 
    
        // Event listener for when the WebSocket connection is established
        socket.addEventListener('open', () => {
          displayMessage('Connected to the WebSocket server');
    
        // Event listener for when a message is received from the server
        socket.addEventListener('message', (event) => {
          displayMessage(`Server: ${event.data}`); // Display the message received from the server
        });
    

    To display the message on the user interface, we’ve created a function called displayMessage. Here’s how it’s defined:

    // Function to display messages in the message container
    function displayMessage(message) {
          const messageDiv = document.getElementById('messages'); // Get the div where messages will be displayed
          const newMessage = document.createElement('p'); // Create a new paragraph element for the new message
          newMessage.textContent = message; // Set the text content of the paragraph to the message
          messageDiv.appendChild(newMessage); // Add the new paragraph to the message container
    }
    

    Now, it’s time to define the sendMessage() function. Firstly, we retrieve a message and then send it to the WebSocket server using the socket.send() method. This transmits the message over the WebSocket connection established earlier, allowing the server to receive it. Next, on the UI, we display the message and clear the input field.

    Thus, the code looks the following way:

        // Function to send a message to the server
        function sendMessage() {
          const message = document.getElementById('messageInput').value; // Get the message from the input field
          socket.send(message); // Send the message over the WebSocket connection
          displayMessage(`You: ${message}`); // Display the message in the UI as sent by the user
          document.getElementById('messageInput').value = ''; // Clear the input field after sending the message
        }
    

    The final step is to set the event listener for when the WebSocket connection closes. To keep it simple, we will log a message to the console.

     socket.addEventListener('close', () => {
          console.log('Disconnected from the WebSocket server'); 
     });
    

    This is what the client-side part looks like:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>WebSocket Client</title>
    </head>
    <body>
      <h1>WebSocket Client</h1>
      <div id="messages"></div>
      <input type="text" id="messageInput" placeholder="Type a message">
      <button onclick="sendMessage()">Send Message</button>
    
      <script>
        // Establish a WebSocket connection to the server
        const socket = new WebSocket('wss://localhost:8080'); // Connect to WebSocket server 
    
        // Event listener for when the WebSocket connection is established
        socket.addEventListener('open', () => {
          displayMessage('Connected to the WebSocket server');
    
        // Event listener for when a message is received from the server
        socket.addEventListener('message', (event) => {
          displayMessage(`Server: ${event.data}`); // Display the message received from the server
        });
    
        // Function to send a message to the server
        function sendMessage() {
          const message = document.getElementById('messageInput').value; // Get the message from the input field
          socket.send(message); // Send the message over the WebSocket connection
          displayMessage(`You: ${message}`); // Display the message in the UI as sent by the user
          document.getElementById('messageInput').value = ''; // Clear the input field after sending the message
        }
    
        // Function to display messages in the message container
        function displayMessage(message) {
          const messageDiv = document.getElementById('messages'); // Get the div where messages will be displayed
          const newMessage = document.createElement('p'); // Create a new paragraph element for the new message
          newMessage.textContent = message; // Set the text content of the paragraph to the message
          messageDiv.appendChild(newMessage); // Add the new paragraph to the message container
        }
    
        // Event listener for when the WebSocket connection is closed
        socket.addEventListener('close', () => {
          console.log('Disconnected from the WebSocket server'); 
        });
      </script>
    </body>
    </html>
    

    What are Server-Sent Events (SSE)?

    Server-Sent Events (SSE) is a technology that enables a web server to push updates to a web page. As part of the HTML5 specification, it functions similarly to WebSockets by using a single, long-lived HTTP connection to deliver data in real-time.

    The concept of SSE originated in 2004, with the Opera team taking the first steps towards implementation in 2006. One of the main limitations of SSE in the early stages was the connection cap imposed by HTTP/1.1, which restricted the number of simultaneous connections a client could establish with a server. But with the introduction of HTTP/2, this limitation was removed. HTTP/2 allows multiple data streams to flow over a single connection through multiplexing, enabling more efficient and scalable communication for SSE.

    Server-sent events (SSE) rely on two fundamental components:

    • EventSource: An interface defined by the WHATWG specification and implemented by modern browsers. It enables the client (typically a browser) to subscribe to server-sent events.
    • EventStream: A protocol that specifies the plain-text format servers must use to send events, ensuring compatibility with the EventSource client for seamless communication.

    As the specification outlines, events can include arbitrary text data and an optional ID and are separated by newlines. Also, SSE events have a dedicated MIME type: text/event-stream. A MIME type (Multipurpose Internet Mail Extensions type) is a standard that indicates the nature and format of a file or data, allowing the browser or server to properly interpret and handle it.

    How do Server-Sent Events Work?

    Server-sent events (SSE) work by establishing a persistent, one-way communication channel from the server to the client over a standard HTTP connection. The client initiates the connection by creating an EventSource object, which sends a request to the server to start streaming data. Once the server receives this request, it responds by sending a continuous stream of updates in a specific text/event-stream format. The client listens for these events, automatically handling any reconnections if the connection is lost.

    SSE is ideal for applications that require real-time updates from the server, such as live news feeds or notifications, as it ensures a constant flow of information with minimal overhead.

    This picture outlines how SSE works

    Advantages of Server-Sent Events

    • Polyfill capability: Server-sent events (SSE) can be implemented using JavaScript in browsers that don’t natively support them. This ensures backward compatibility by leveraging the standard SSE interface instead of creating a custom alternative.

    • Automatic reconnection: SSE connections are designed to reconnect automatically after interruption. Thus, they reduce the need for extra code to handle this essential functionality.

    • Firewall-friendly: SSEs work seamlessly with corporate firewalls that perform packet inspection, making them a reliable choice for enterprise applications.

    Disadvantages of Server-Sent Events

    • Data format restrictions: SSE is restricted to transmitting messages in UTF-8 format, as it does not support binary data.

    • Connection limits: Browsers cap the number of simultaneous SSE connections to six per client. This limitation becomes problematic when multiple tabs require active SSE connections. For more details and potential workarounds, refer to this StackOverflow thread: Server-Sent Events and browser limits.

    • One-way communication: SSE supports only server-to-client messaging, making it ideal for read-only real-time applications like stock tickers. However, this unidirectional nature can be a constraint for more interactive real-time applications.

    Use Cases for SSE

    Server-sent events are widely used in applications where real-time data delivery is crucial. SSE enables the server to push updates to the client automatically, making it ideal for applications that require live information streams. From news feeds to financial dashboards, SSE ensures that users receive the latest content without constant page refreshes.

    Here are some common use cases for using SSE:

    1. Social Media Feeds

    Social media platforms leverage SSE to push new posts instantly, likes, and comments to users’ feeds, providing a more dynamic and engaging user experience. A great example is Twitter’s (X’s) real-time feed implementation, which allows them to push real-time updates to the browser.

    2. Enterprise Monitoring System

    SSE enables financial monitoring systems and other real-time applications to deliver live data updates efficiently. For instance, Netflix’s open-source Hystrix, a well-known component for microservice monitoring and circuit breaking, includes a web dashboard that displays real-time performance metrics and circuit status. This dashboard uses SSE to push performance data in real-time, ensuring that users can monitor the health and performance of microservices as they happen. The dashboard leveraging SSE provides an efficient, low-latency solution for updating performance data without needing constant manual refreshing or polling.

    3. Generative AI

    SSE technology plays a key role behind the scenes when interacting with Generative AI chatbots like ChatGPT and Gemini. For instance, when a user requests ChatGPT to write an article on a specific topic, the server starts processing the request and generates the article progressively, often in chunks rather than all at once.

    During this process, ChatGPT’s server utilizes SSE to push each part of the article to the client in real-time, allowing the user to see the content appear as it is being generated.

    How to Implement Server-Sent Events using Node.js

    In this section, we’ll explore how to implement SSE using Node.js, a popular JavaScript runtime, to push updates to the client in real-time. We’ll set up a basic server and send live data to the browser using SSE.

    We’ll start with the client-side (HTML/JavaScript). First, we’ll create a new EventSource object to listen for events from the server.

    const evtSource = new EventSource("sse-demo.js");
    

    The URL "sse-demo.js" is the path to the server-side script that will generate the events. But if the event generator script were hosted on a different origin, we would need to provide an additional configuration for cross-origin requests.

    // If the event generator script is hosted on a different origin (cross-origin request):
    const evtSource = new EventSource("//api.example.com/sse-demo.js", {
      withCredentials: true,  // Sends cookies, authorization headers with the request to the server
    });
    

    This version ensures that cookies and authorization headers are sent with the request, enabling secure communication and making sure that credentials can be included in cross-origin requests. withCredentials: true ensures that authentication is handled correctly if needed.

    Next, let’s set up an event listener to handle the message when it is received. To keep things simple, we will display the message on the user interface by adding it as a new list item.

    // When a message event is received
    evtSource.onmessage = (event) => {
      // Create a new list item element to display the message
      const newElement = document.createElement("li");
    
      // Get the reference to the unordered list element where messages will be displayed
      const eventList = document.getElementById("list");
    
      // Set the text content of the new list item to the message received
      newElement.textContent = `message: ${event.data}`;
    
      // Append the new list item to the event list (ul) in the HTML
      eventList.appendChild(newElement);
    };
    

    Let’s also add an event listener for a custom “ping” event. Again, we will simply add new data to the list and display it on the page. Thus, when the custom event is received, a new list item (<li>) is created.

    The event data, which contains a time property, is parsed from JSON, and the time is displayed in the list item. This new list item is then appended to an unordered list (<ul>) in the HTML, allowing the “ping” event data to be shown on the user interface.

    // Add an event listener for a custom event type, "ping"
    evtSource.addEventListener("ping", (event) => {
      // Create a new list item element to display the ping event
      const newElement = document.createElement("li");
    
      // Get the reference to the unordered list element where ping events will be displayed
      const eventList = document.getElementById("list");
    
      // Parse the event data as JSON (assuming it contains a time property)
      const time = JSON.parse(event.data).time;
    
      // Set the text content of the new list item to display the ping time
      newElement.textContent = `ping at ${time}`;
    
      // Append the new list item to the event list (ul) in the HTML
      eventList.appendChild(newElement);
    });
    

    Make sure that your code for the client-side part looks like this:

    // Create a new EventSource to listen for events from the server
    const evtSource = new EventSource("sse-demo.js");
    
    /* If the event generator script is hosted on a different origin (cross-origin request):
    const evtSource = new EventSource("//api.example.com/sse-demo.js", {
      withCredentials: true,  // Sends cookies, authorization headers with the request to the server
    });
    */
    // When a message event is received
    evtSource.onmessage = (event) => {
      // Create a new list item element to display the message
      const newElement = document.createElement("li");
    
      // Get the reference to the unordered list element where messages will be displayed
      const eventList = document.getElementById("list");
    
      // Set the text content of the new list item to the message received
      newElement.textContent = `message: ${event.data}`;
    
      // Append the new list item to the event list (ul) in the HTML
      eventList.appendChild(newElement);
    };
    
    // Add an event listener for a custom event type, "ping"
    evtSource.addEventListener("ping", (event) => {
      // Create a new list item element to display the ping event
      const newElement = document.createElement("li");
    
      // Get the reference to the unordered list element where ping events will be displayed
      const eventList = document.getElementById("list");
    
      // Parse the event data as JSON (assuming it contains a time property)
      const time = JSON.parse(event.data).time;
    
      // Set the text content of the new list item to display the ping time
      newElement.textContent = `ping at ${time}`;
    
      // Append the new list item to the event list (ul) in the HTML
      eventList.appendChild(newElement);
    });
    

    Now, let’s code the server-side part with Node.js and Express.js. Express is a minimal and flexible web application framework for Node.js. It simplifies the creation of server-side applications by providing robust features like routing, middleware support, and handling HTTP requests and responses. It helps streamline the development of web APIs and websites, making it perfect for our tutorial.

    Note that you’ll need to go to the official Express.js documentation and install it on your machine if you don’t have it installed already.

    Then, head over to the IDE and import the Express module, which allows us to create an instance of the Express application.

    // Import the Express module
    const express = require('express');
    // Create an instance of the Express application
    const app = express();
    

    It is considered good practice to specify the port number at the top of the file to make it easy to configure and modify later. This approach improves code readability and maintainability, allowing you to quickly change the port number without searching through the entire file. It also enables better flexibility when deploying the application in different environments (for example, development, staging, production).

    For this tutorial, we have set the port number to 3000.

    // Define the port number for the server to listen on
    const port = 3000;
    

    Now, let’s set up the server-side part with Node.js and Express to handle SSE requests. We define a route (/sse) that will send a continuous stream of events to the client.

    // Define a route that handles requests to /sse endpoint
    app.get('/sse', (req, res) => {
    //....
    });
    

    For the server to communicate with the client using SSE, we need to set specific HTTP headers:

    • Content-Type: We specify 'text/event-stream' to let the client know that the response is an SSE stream.

    • Cache-Control: Setting it to 'no-cache' ensures the client gets fresh data each time, without caching.

    • Connection: This is set to 'keep-alive' to maintain the connection open for continuous data transmission.

    app.get('/sse', (req, res) => {
      // Set the Content-Type header to 'text/event-stream' to indicate that 
      // the response will be an SSE stream
      res.setHeader('Content-Type', 'text/event-stream');
    
      // Prevent caching of the stream (important to ensure real-time updates)
      res.setHeader('Cache-Control', 'no-cache');
    
      // Keep the connection alive to continuously send events
      res.setHeader('Connection', 'keep-alive');
    });
    

    You can use res.flushHeaders() to send the headers immediately. Thus, the client can begin listening for events without delay.

    To add a bit of flair, let’s send a new SSE message every second, including the number of the message being sent. For this, we will initialize a variable counter , as well as to use setInterval to send a new message every second (1000ms).

    app.get('/sse', (req, res) => {
      // Set the Content-Type header to 'text/event-stream' to indicate that 
      // the response will be an SSE stream
      res.setHeader('Content-Type', 'text/event-stream');
    
      // Prevent caching of the stream (important to ensure real-time updates)
      res.setHeader('Cache-Control', 'no-cache');
    
      // Keep the connection alive to continuously send events
      res.setHeader('Connection', 'keep-alive');
    
      // Send the headers immediately, so the client starts listening for events
      res.flushHeaders();
    
      // Initialize a counter variable for the messages
      let counter = 0;
    
      // Use setInterval to send a new message every second (1000ms)
      setInterval(() => {
        // Send a new SSE message, incrementing the counter each time
        // Each message is prefixed with 'data: ' and followed by the message content
        res.write(`data: This is message ${counter++}nn`);
      }, 1000); // This interval runs every 1000 milliseconds (1 second)
    });
    

    The last step is to start the Express server the following way:

    app.listen(port, () => {
      console.log(`Server running at http://localhost:${port}`);
    });
    

    And that’s it! Make sure your server-sent code looks like this:

    // Import the Express module
    const express = require('express');
    // Create an instance of the Express application
    const app = express();
    
    // Define the port number for the server to listen on
    const port = 3000;
    
    // Define a route that handles requests to /sse endpoint
    app.get('/sse', (req, res) => {
      // Set the Content-Type header to 'text/event-stream' to indicate that 
      // the response will be an SSE stream
      res.setHeader('Content-Type', 'text/event-stream');
    
      // Prevent caching of the stream (important to ensure real-time updates)
      res.setHeader('Cache-Control', 'no-cache');
    
      // Keep the connection alive to continuously send events
      res.setHeader('Connection', 'keep-alive');
    
      // Send the headers immediately, so the client starts listening for events
      res.flushHeaders();
    
      // Initialize a counter variable for the messages
      let counter = 0;
    
      // Use setInterval to send a new message every second (1000ms)
      setInterval(() => {
        // Send a new SSE message, incrementing the counter each time
        // Each message is prefixed with 'data: ' and followed by the message content
        res.write(`data: This is message ${counter++}nn`);
      }, 1000); // This interval runs every 1000 milliseconds (1 second)
    });
    
    // Start the Express server and listen on the specified port
    app.listen(port, () => {
      console.log(`Server running at http://localhost:${port}`);
    });
    

    WebSockets vs Server-Sent Events

    The goal of data transfer methods is to load and display large datasets as quickly as possible. This ensures that the user perceives the response as instant and provides smooth navigation and a pleasant user experience.

    Jakob Nielsen, a retired principal and co-founder of the Nielsen Norman Group, outlined three key time limits that developers should consider when optimizing web and application performance in his book Usability Engineering. In short, 0.1 seconds is the threshold for users to feel that the system is responding instantaneously, meaning that no special feedback is required other than simply displaying the result.

    Vera Didenko, Software Architect and Developer at Flexmonster conducted research to identify the most efficient data transfer protocol and, based on the 100-millisecond constraint, calculated the time budget for each process, ultimately choosing WebSockets as the optimal method for loading and updating the data.

      Extended tolerable response time from Jakob Nielsen’s “Usability Engineering”.

    For research purposes, Vera created an application using .NET Core and SignalR to test and visually compare the performance of WebSockets and Server-Sent Events to discover which data transfer approach is the most performance-efficient. SignalR is an open-source library that simplifies real-time web functionality.

    After running several tests for all methods simultaneously while increasing the number of calls each time, the test results were gathered in JSON format and fed to the amCharts library. Below are the test results for 100, 1000, and 10000 calls:

    The performance of WebSockets and Server-Sent Events

    The experiment results show that WebSockets perform best for this task, emerging as the most performance-efficient data transfer technology in the simulated scenarios.

    Which is Better: Server-Sent Events or WebSockets?

    SSE is a simpler solution, but it isn’t extensible: if your web application requirements were to change, it would likely need to be refactored using WebSockets. Also, with AI integration SSE becomes even more powerful and secure.

    Although WebSocket technology presents more upfront work, it’s a more versatile and extensible framework, so it’s a better option for complex applications that are likely to add new features over time.

    Feature

    WebSockets

    Server-Sent Events 

    Communication

    Full-duplex (two-way)

    One-way (server-to-client)

    Data Type Support

    Binary and text

    Text (UTF-8 encoded only)

    Connection Limits

    Limited by server resources

    Limited by browser (e.g. 6 tabs)

    Reconnection

    Requires manual handling

    Automatic

    Protocol

    Custom, low-level protocol

    Built on HTTP

    Firewall Handling

    May face problems

    Work seamlessly

    Use Case Examples

    real-time, event-driven communication between clients and servers, such as online games, chats, etc

    streaming data uni-directionally (i.e., “one direction”) from server to client for streaming data like stock quotes, bitcoin prices, etc

    In practice, many developers prefer WebSockets even for scenarios requiring receiving information rather than opting for SSE. This preference is not solely due to the limitations of SSE—such as its reliance on keeping a connection open for continuous data flow—but also because WebSockets offer greater versatility and are often considered more future-proof.

    For instance, popular platforms like Reddit and Trello choose WebSockets to receive data (Reddit and Trello only send information to users when they are offered to subscribe to another person).

    From personal experience, I can point out that SSE data often doesn’t appear in the developer tools, making it harder to debug and inspect. You can verify this by checking a web application like ChatGPT, where no SSE data sent by the server is visible in the developer tools network tab. This lack of transparency can make working with SSE more challenging than the more straightforward, visible data flow provided by WebSockets.

    Wrapping Up

    I hope this article was both interesting and useful to you! If you want to further strengthen your knowledge and take your skills to the next level, I highly recommend diving into real-life projects.

    Personally, I found these on freeCodeCamp to be really useful and even a bit challenging: How to Build a Logging Web App with Server-Sent Events, RxJS, and Express and Learn WebSockets with Socket.IO. Not only will these projects give you hands-on experience, but they will also provide you with valuable new insights and skills that you can apply to future development challenges.

    Happy coding, and keep learning!

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleHisense’s new laser projector is so sharp and color-accurate, it may just replace your 4K TV
    Next Article A Beginner’s Guide to Terraform – Infrastructure-as-Code in Practice

    Related Posts

    Security

    China-Linked Hackers Exploit SAP and SQL Server Flaws in Attacks Across Asia and Brazil

    May 31, 2025
    Security

    New Apache InLong Vulnerability (CVE-2025-27522) Exposes Systems to Remote Code Execution Risks

    May 31, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    This Elden Ring DLC Scadutree Fragments map shows the fastest route to their locations in Shadow of the Erdtree

    Development

    How to Create Partitions in Linux Using fdisk Command

    Development

    Salesforce AI Research Introduces AGUVIS: A Unified Pure Vision Framework Transforming Autonomous GUI Interaction Across Platforms

    Development

    Ursnif Trojan Campaign Targets U.S. Professionals via Stealthy Spam Attacks

    Development

    Highlights

    Development

    Understanding Salesforce Push Topics: Real-Time Data Streaming Made Simple

    February 9, 2025

    As a Salesforce developer with years of experience implementing real-time solutions, I’ve found Push Topics…

    Linux Icon Pack Papirus Gets First Update in 8 Months

    February 2, 2025

    Le notizie minori del mondo GNU/Linux e dintorni della settimana nr 11/2025

    March 16, 2025

    Kinetix: An Open-Ended Universe of Physics-based Tasks for Reinforcement Learning

    November 18, 2024
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.