First steps with Server Side Events

Discover the power of Server-Side Events (SSE). This technology allows your website to receive real-time updates from the server. I successfully used SSE in my project and outlined how you can integrate it into your next Node.js + React project.

5 minutes
Hero image

Background

As part of a project, I faced the challenge of informing the user on the front end after the background task they had initiated had been completed. In the past, I first came up with two approaches to a solution: push or pull—web sockets or regular, client-side polling. But thanks to lunch with a technician friend, I heard about server-side events, a practical alternative if the conditions are right.

What are server-side events?

Server-side events (also known as server-sent events) are a technology that allows a browser client to receive real-time updates from the server. This enables efficient and asynchronous communication between the client and the server.

The basic steps:

  1. Client request: The client (e.g., a web browser) requests the server to explore events.
  2. Server response: The server tells the client a list of available events and a URL for updating this list.
  3. Event streaming: When a new event occurs on the server, the server informs the client and sends the current event directly to the client.

Advantages of SSE

  • Efficient communication: The asynchronous transmission of data reduces the load on the network.
  • Real-time updates: The client receives real-time data, which enables efficient communication.
  • Scalability: SSE can be easily integrated into large applications because the load is negligible, and the browser automatically ensures the connection between client and server, including reconnection if the connection is lost.
  • Ease of implementation: Implementing SSE is simple and understandable, which makes it a popular technology for web applications.
    Flexibility: SSE can be used in various application areas, such as live website updates or real-time data visualization.

How do I use SSE in my project?

Integration in Node.js

Using the NPM package better-sse, you can easily register routes in Node.js and Express to which the client can log in. A session is started for each client that can be registered on a channel. I publish events on a channel that is broadcast to all registered clients. I can specify the initial values ​​for the respective channel in the route to fill the clients with data when they log in.

The associated code example shows my helper method, which accepts an Express router as a parameter and registers the routes there. I have also outsourced the SSE channels to their helper classes.

// ServerSideEventRegisterRoutes.ts
import { createSession } from 'better-sse'
import type { Router } from 'express'

import DashboardChannel from '@/serverSideEvents/DashboardChannel'

const ServerSideEventRegisterRoutes = (app: Router) => {
  app.get('/sse', async (req, res) => {
    const session = await createSession(req, res)
    PrescriptionChannel.prescriptionChannel.register(session)
    DashboardChannel.dashboardChannel.register(session)

    // initialize sse stream
    PrescriptionChannel.publishLatestScanDateTime()
    DashboardChannel.publishDashboardData()
  })
}
// ServerSideEventRegisterRoutes.ts
import { createSession } from 'better-sse'
import type { Router } from 'express'

import DashboardChannel from '@/serverSideEvents/DashboardChannel'

const ServerSideEventRegisterRoutes = (app: Router) => {
  app.get('/sse', async (req, res) => {
    const session = await createSession(req, res)
    PrescriptionChannel.prescriptionChannel.register(session)
    DashboardChannel.dashboardChannel.register(session)

    // initialize sse stream
    PrescriptionChannel.publishLatestScanDateTime()
    DashboardChannel.publishDashboardData()
  })
}
// DashboardChannel.ts
import { createChannel } from 'better-sse'

//Here you define your channel data format
type DashboardData = {...}

const dashboardChannel = () => {
  const dashboardChannel = createChannel()
  let dashboardData: DashboardData = { ... }

  // update internal state of dashboard data
  const setDashboardData = (updatedDashboardData: Partial<DashboardData>) => {
    dashboardData = {
      ...dashboardData,
      ...updatedDashboardData,
    }
  }

  // encapsulate logic to fetch dashboard data - alternatively, use only the setDashboardData from the callee
  const fetchDashboardData = async () => {
    setDashboardData({ ... })
    publishDashboardData()
  }

  // trigger to update all registered sessions of the channel "dashboard data."
  const publishDashboardData = () => {
    dashboardChannel.broadcast(dashboardData, 'dashboardData')
  }

  // Make the methods defined above accessible
  return {
    dashboardChannel,
    setDashboardData,
    fetchDashboardData,
    publishDashboardData,
  }
}

export default dashboardChannel()

Note for Swagger-Ui-Express users: you must register the SSE and Swagger routes. I haven't found a way to marry the routes with the Swagger TSOA syntax.

Integration in React Frontend

I register with the backend in the useEffect hook of the App() in my frontend code. This ensures that the connection is established when my application starts. If I connect my state to a Redux store, I can access it anywhere in the component tree and display the data.

// App.ts
function App() {
  useEffect(() => {
    const eventSource = new EventSource(apiUrl)

    // Listen to events on the channel "dashboardData"
    eventSource.addEventListener('dashboardData', (event: MessageEvent) => {
      // ensure to sent valid json from server, else add try+catch here
      const eventData = JSON.parse(event.data)

      // Work with eventData, like storing it and rendering it somewhere in your app
    })
  }, []) // will run once on application start

// ...
}

Conclusion

With the method shown here, I have a simple way to provide my clients with updates from the backend. The basic approach I show will provide messages to all connected clients about the completed task a single user triggered. This can be a valid use case. But maybe you want to show the message only to the user originally triggered the event.

The way is to send the active user's ID with the request, write it later in the event as a date in the JSON, and compare it with the current ID in the addEventListener. If you want me to outline the solution idea in more detail, I'd be happy to contact you for digital coffee or write me a comment.

call to action background image

Subscribe to my newsletter

Receive once a month news from the areas of software development and communication peppered with book and link recommendations.