DEV Community

tom-takeru
tom-takeru

Posted on

The simplest demo on SSE(Server-Send Events)

Introduction

Server-Sent Events (SSE) is a web technology that allows a server to push real-time updates to a client over HTTP. Unlike WebSockets, SSE is simpler to implement as it uses a one-way communication channel from the server to the browser and works over a regular HTTP connection. It is especially useful for applications requiring periodic updates, such as live scores, notifications, or real-time monitoring dashboards.

I created this demo because I’m currently developing an application where AIs discuss various topics. I wanted to implement some stream-like features and discovered the technology called "SSE".

Demo Overview

This demo showcases how Server-Sent Events (SSE) can be used to send real-time updates from an API to the browser. In this example, the browser displays a series of sample messages sent by the server. The simplicity of this demo makes it an excellent starting point for understanding how SSE works and integrating it into your projects.

Demo Video

Below is a video demonstrating how the Server-Sent Events (SSE) demo works in real time. Watching this video will give you a better understanding of how the client and server interact to provide real-time updates.

Demo Video

Implementation

The core implementation of the Server-Sent Events (SSE) demo is split into two parts: the frontend and the backend. The full source code is available on GitHub: sse-demo repository.

Frontend (ui/src/app/page.tsx)

The frontend is built with React and provides buttons to start and stop the SSE connection, displaying real-time messages from the server. Here are the main highlights:

  1. Connecting to SSE: The handleStartConnection function creates an EventSource object connected to the /events endpoint. It listens for messages, open events, and error events:

    • onmessage: Handles incoming messages and updates the messages state.
    • onopen: Logs when the connection is established.
    • onerror: Handles errors, logging details and closing the connection if needed.
  2. Stopping the Connection: The handleStopConnection function closes the SSE connection and cleans up.

  3. UI: The page includes a simple interface with start/stop buttons and a list to display messages.

"use client";

import type React from "react";
import { useState } from "react";

const App: React.FC = () => {
  const [messages, setMessages] = useState<string[]>([]);
  const [eventSource, setEventSource] = useState<EventSource | null>(null);

  const handleStartConnection = () => {
    const newEventSource = new EventSource("http://localhost:8080/events");

    const handleOnMessage = (event: MessageEvent) => {
      console.log("onmessage", event.data);
      setMessages((prev) => [...prev, event.data]);
    };

    const handleOnOpen = () => {
      console.log("Connection established");
    };

    const handleOnError = (error: Event) => {
      console.error("onerror", error);
      console.log("readyState:", newEventSource.readyState);
      console.log("Connection error occurred.");
      newEventSource.close();
      setEventSource(null);
    };

    const handleOnClose = () => {
      console.log("Connection is being closed by the server.");
      newEventSource.close();
      setEventSource(null);
    };

    newEventSource.onmessage = handleOnMessage;
    newEventSource.onopen = handleOnOpen;
    newEventSource.onerror = handleOnError;
    newEventSource.addEventListener("close", handleOnClose);

    setEventSource(newEventSource);
  };

  const handleStopConnection = () => {
    if (eventSource) {
      eventSource.close();
      setEventSource(null);
      console.log("Connection closed");
    }
  };

  return (
    <div>
      <h1>Server-Sent Events Demo</h1>
      <button
        type="button"
        onClick={handleStartConnection}
        disabled={!!eventSource}
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
      >
        Start Connection
      </button>
      <button
        type="button"
        onClick={handleStopConnection}
        disabled={!eventSource}
        className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 ml-2"
      >
        Stop Connection
      </button>
      <ul>
        {messages.map((message, index) => (
          <li key={`${index}-${message}`}>{message}</li>
        ))}
      </ul>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Backend (api/main.go)

The backend is built using the Gin framework for Go and includes the following key features:

  1. CORS Configuration: The backend uses the Gin CORS middleware to allow connections during debugging.

  2. SSE Endpoint: The /events endpoint streams a series of pre-defined messages to the client with a delay between each message. Key components:

    • Headers are set to specify the text/event-stream content type.
    • Messages are sent in a loop, with a 2-second delay between each message.
    • A final close event signals the end of the connection.
package main

import (
    "fmt"
    "time"

    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"*"}, // デバッグ用
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
        AllowHeaders:     []string{"Origin", "Content-Type"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
    }))

    r.GET("/events", func(c *gin.Context) {
        c.Writer.Header().Set("Content-Type", "text/event-stream")
        c.Writer.Header().Set("Cache-Control", "no-cache")
        c.Writer.Header().Set("Connection", "keep-alive")

        fmt.Fprintf(c.Writer, "retry: 0\n\n")
        c.Writer.Flush()

        messages := []string{
            "(Mike) Hello",
            "(Tom) Hi!",
            "(Mike) How are you?",
            "(Tom) I'm good, thanks! How about you?",
            "(Mike) I'm doing well, thanks for asking.",
            "(Tom) What's your plan for today?",
            "(Mike) I have a meeting in the afternoon.",
            "(Tom) Sounds good. Let's catch up later.",
            "(Mike) Sure, see you later!",
            "(Tom) Bye!",
        }

        for _, msg := range messages {
            // NOTE: This is just for demonstration purposes.
            time.Sleep(2 * time.Second)
            fmt.Fprintf(c.Writer, "data: %s\n\n", msg)
            c.Writer.Flush()
        }

        c.Writer.Write([]byte("event: close\ndata: \n\n"))
        c.Writer.Flush()
    })

    r.Run("0.0.0.0:8080")
}
Enter fullscreen mode Exit fullscreen mode

How to Run It

To run this demo, please refer to the README.md file in the GitHub repository. It contains step-by-step instructions for setting up and running both the frontend and backend of the project.

Conclusion

This demo provides a simple yet effective introduction to Server-Sent Events (SSE), demonstrating how to stream real-time messages from a server to a browser. By focusing on the basics, it’s designed to help you quickly understand the core concepts and start experimenting with SSE in your own projects.

If you’re interested in trying it out or building upon this example, check out the full source code on GitHub: sse-demo repository.

Top comments (0)