top of page
Search

Using Server-Side Events with Spring Boot and React for Immediate Notifications

Using Server-Sent Events with Spring Boot and React for Immediate Notifications


This article demonstrates how to build a real-time notification system using Server-Sent Events (SSE) with a Spring Boot backend and a React frontend, enabling immediate notifications. SSE is a lightweight, unidirectional communication protocol that allows the server to push updates to the client without explicit client requests, perfect for real-time notifications.


1. Spring Boot Backend:


We'll create a Spring Boot REST controller to handle SSE connections and send events to connected clients.


```java

import org.springframework.http.MediaType;

import org.springframework.web.bind.annotation.*;

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;


import java.io.IOException;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


@RestController

public class NotificationController {


    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    private final ExecutorService executor = Executors.newFixedThreadPool(10); // Adjust pool size as needed


    @GetMapping(value = "/notifications", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

    public SseEmitter streamNotifications(@RequestParam String userId) {

        SseEmitter emitter = new SseEmitter();

        emitters.put(userId, emitter);


        emitter.onCompletion(() -> emitters.remove(userId));

        emitter.onError(e -> {

            emitters.remove(userId);

            e.printStackTrace(); // Use a proper logger in production

        });

        emitter.onTimeout(() -> {

            emitters.remove(userId);

            emitter.complete();

        });


        try {

            emitter.send(SseEmitter.event().name("connected").data("Connection established!"));

        } catch (IOException e) {

            e.printStackTrace(); // Log the error

        }


        return emitter;

    }


    @PostMapping("/sendEvent") // Use POST for sending events

    public String sendEvent(@RequestParam String userId, @RequestBody String message) { // Use @RequestBody

        SseEmitter emitter = emitters.get(userId);

        if (emitter != null) {

            executor.execute(() -> {

                try {

                    emitter.send(SseEmitter.event().name("notification").data(message)); // Named event

                } catch (IOException e) {

                    emitters.remove(userId);

                    e.printStackTrace(); // Log the error

                }

            });

            return "Event sent!";

        }

        return "No emitter found for this user.";

    }


    // Example of triggering an event from a service (or any other part of your application)

    // @Service

    // public class MyService {

    //    @Autowired

    //    private NotificationController notificationController;


    //    public void doSomething() {

    //        String userId = "123"; // Get the user ID

    //        String message = "Something happened!";

    //        notificationController.sendEvent(userId, message); // Call the controller

    //    }

    //}

}

```


Key Changes and Explanations (Backend):


  *`@PostMapping` for `/sendEvent`:**  Using `POST` is generally better for sending data to the server, as it's more appropriate for actions that modify server state.

  *`@RequestBody`:**  This annotation allows you to send the message in the request body (e.g., as JSON), which is cleaner and more flexible than using query parameters for the message content.

  *Named Event:**  The event is now sent with a name ("notification"). This is highly recommended as it allows the client to distinguish between different types of events.

  *Example Service Call:** The commented-out `MyService` example shows how you would call `sendEvent` from another part of your application (e.g., a service, scheduled task, or after some action occurs).


2. React Frontend:


```javascript

import React, { useState, useEffect } from 'react';


function Notifications() {

  const [messages, setMessages] = useState([]);

  const userId = "123"; // Replace with actual user ID


  useEffect(() => {

    if (!userId) return;


    const eventSource = new EventSource(`/notifications?userId=${userId}`);


    eventSource.onmessage = (event) => {

      console.log("Received event:", event);


      if (event.data === "Connection established!") {

        console.log("SSE connection established");

        return;

      }


      if (event.type === "notification") { // Check event type

        try {

          const message = JSON.parse(event.data); // Parse if JSON

          setMessages((prevMessages) => [...prevMessages, message]);

        } catch (error) {

          setMessages((prevMessages) => [...prevMessages, event.data]); // Fallback to plain text

        }

      }

    };


    eventSource.onerror = (error) => {

      console.error("SSE error:", error);

      eventSource.close();

    };


    return () => {

      eventSource.close();

    };

  }, [userId]);


  const sendTestEvent = async () => {

    const message = { text: "Hello from React!", value: 123 }; // Example JSON message

    try {

      const response = await fetch(`/sendEvent?userId=${userId}`, {

        method: 'POST',

        headers: {

          'Content-Type': 'application/json', // Important: Set Content-Type

        },

        body: JSON.stringify(message), // Send message in the body

      });


      if (response.ok) {

        console.log("Test event sent successfully");

      } else {

        console.error("Failed to send test event");

      }

    } catch (error) {

        console.error("Error sending test event:", error);

    }

  };


  return (

    <div>

      <h2>Notifications</h2>

      <button onClick={sendTestEvent}>Send Test Event</button>

      <ul>

        {messages.map((message, index) => (

          <li key={index}>{message.text || message}</li> // Display message

        ))}

      </ul>

    </div>

  );

}


export default Notifications;

```


Key Changes and Explanations (Frontend):


  *`POST` Request:**  The `sendTestEvent` function now uses a `POST` request to `/sendEvent`.

  *`Content-Type` Header:**  The `Content-Type` header is set to `application/json` to indicate that you are sending JSON data in the request body.  This is crucial.

  *`JSON.stringify()`:** The message is converted to a JSON string using `JSON.stringify()` before being sent in the request body.

  *Handling Named Events:** The `onmessage` handler now checks `event.type === "notification"` to ensure it's handling the correct type of event.  This is essential when you have multiple event types.

  *Display Logic:** The display logic now correctly handles the JSON message format (`message.text`).


This revised version provides a more robust and practical implementation of server-sent events with Spring Boot and React, including best practices for sending events (using `POST` and JSON), handling different event types, and managing the SSE connection effectively.  It's now much closer to a production-ready implementation.

 
 
 

Recent Posts

See All
What we can learn from cats

That's a fascinating observation, and you've touched upon something quite profound about the apparent inner peace that some animals seem...

 
 
 

Comentários


Post: Blog2_Post

Subscribe Form

Thanks for submitting!

©2020 by LearnTeachMaster DevOps. Proudly created with Wix.com

bottom of page