Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send a Server Sent Event(SSE) each time when an api end point is hit in NodeJs

I am trying to implement a way to send a server sent event(SSE) in NodeJs each time an endpoint of my server is hit.

Here is how I achieved that

//...code...
import EventSource from "eventsource";

const EventEmitter = require('events');
const events = new EventEmitter();
// events.setMaxListeners(10);


// api end point route from where the new order requests will be coming
router.route('/new-order')
  .post((req, res) => {
    const orderData = req.body.data;
    //... save order and emit an event to send response to the client react app
    events.emit('newOrder', orderData);
  });

// define a function to send SSE events to the client
const sse = res => data => {
  const dataToSend = JSON.stringify(data);
  res.write(`data:${dataToSend}`);
  res.write("\n\n");
};

// define an EventSource route for the client to be connected for new events
router.route('/sse')
  .get((req, res) => {

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

    res.write("\n");

    const sendSSE = sse(res);
    /*
    PROBLEMATIC CODE-- EACH request to /sse route adds new event listener,
    and in minutes, it exceeds the limit of maxListeners, and I get the warning 
    to increase the max event listeners limit by using setMaxListeners()
    even if only 1 user is using the front end app.
    */
    events.on('newOrder', sendSSE);
  });

//...Code...


// Client Side
const newOrderReceived = (e) => {
  console.log(JSON.parse(e.data));
};

if ( !window.eventSource ) {
  window.eventSource = new EventSource('https://example.com/sse');
  window.eventSource.onmessage = newOrderReceived;
}

But the problem is that the maxListeners exhaust at a very rapid speed even if only 1 user is using the app.

If I change the event binding code to

events.once('newOrder', sendSSE);

the events maxListeners error disappears but it does not notify my app after the first order. I am unable to find a way to bind the event out of my /sse route because I need to send the data by

res.write(`data:${dataToSend}`);
res.write("\n\n");

and that res object is available only inside a route in my case /sse.

What could be the solution to this problem or is there a better approach available to send server sent event(SSE) in NodeJS to notify my front end app, each time I get a request on my API end point?

Any help would be much appreciated.

P.S: All the tutorials/guides I saw while searching on this issue are implementing a setInterval inside the route to send data, I didn't find a tutorial which explains how to send data in response to an Event on the server.

like image 370
Abid Ali Avatar asked Oct 19 '25 01:10

Abid Ali


1 Answers

Here is how I solved this problem

// Events.js File
const EventEmitter = require('events');
const events = new EventEmitter();
events.setMaxListeners(parseInt(config.MAX_EVENT_LISTENERS));

events.on('error', (err) => {
  // handle Error
});

// event to fire on each new order.
events.on('newOrder', (data) => {
  // and in the events.js file, I access it from the global scope of Node
  // so this is the `res` object from the route where I want to send SSE.
  if ( global.sseResponse ) {
    const sendSSE = sse(global.sseResponse);
    sendSSE(data);
  } else {
    // no sse listener, do something else,
  }
});

module.exports = events;

and in my routes file

// My routes file where I want to use this
router.route('/sse')
  .get(
    (req, res) => {

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

      res.write("\n");
      // Node has a global variable as browsers have a window variable, I attached the res object of my route to
      // the Node's global object to access it outside of this route
      global.sseResponse = res;
      const sendSSE = ApiController.sse(res);
      // keep the connection on
      setInterval(() => {
        sendSSE({ message: 'keep-connection-alive' });
      }, 5000);
    });

And here is the function which I am using to send sse, you can write it anywhere you want and export it from there to use on multiple places

// function to send server sent events (sse)
const sse = res => data => {
  const dataToSend = JSON.stringify(data);

  res.write(`data:${dataToSend}`);
  res.write("\n\n");

  // this is the important part if using the compression npm module
  res.flush();
},

Using this approach solved my problem, Hope it will help someone else as well.

like image 198
Abid Ali Avatar answered Oct 20 '25 15:10

Abid Ali



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!