27

I've looked around and it seems as if all the ways to implement SSEs in Node.js are through more complex code, but it seems like there should be an easier way to send and receive SSEs. Are there any APIs or modules that make this simpler?

Jasch1
  • 525
  • 2
  • 8
  • 22
  • 2
    what about using sockets? – Daniel Mar 27 '16 at 16:26
  • 1
    @Daniel, SSE runs on HTTP and HTTP runs on TCP sockets :) "websockets" would be more appropriate. However there are a buch of libraries that do SSE today. I personally created https://www.npmjs.com/package/@toverux/expresse because I was unsatisfied by the existing packages. – Morgan Touverey Quilling May 29 '17 at 15:57
  • 1
    I was looking at something simple, couldn't find anything so I made a dead simple SSE server using nodejs. its at https://github.com/TheSalarKhan/node-sse-server To use it in production you should add an authentication middleware though. – Salar Khan Jan 13 '20 at 19:43

7 Answers7

67

Here is an express server that sends one Server-Sent Event (SSE) per second, counting down from 10 to 0:

const express = require('express')

const app = express()
app.use(express.static('public'))

app.get('/countdown', function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  })
  countdown(res, 10)
})

function countdown(res, count) {
  res.write("data: " + count + "\n\n")
  if (count)
    setTimeout(() => countdown(res, count-1), 1000)
  else
    res.end()
}

app.listen(3000, () => console.log('SSE app listening on port 3000!'))

Put the above code into a file (index.js) and run it: node index

Next, put the following HTML into a file (public/index.html):

<html>
<head>
  <script>
  if (!!window.EventSource) {
    var source = new EventSource('/countdown')

    source.addEventListener('message', function(e) {
      document.getElementById('data').innerHTML = e.data
    }, false)

    source.addEventListener('open', function(e) {
      document.getElementById('state').innerHTML = "Connected"
    }, false)

    source.addEventListener('error', function(e) {
      const id_state = document.getElementById('state')
      if (e.eventPhase == EventSource.CLOSED)
        source.close()
      if (e.target.readyState == EventSource.CLOSED) {
        id_state.innerHTML = "Disconnected"
      }
      else if (e.target.readyState == EventSource.CONNECTING) {
        id_state.innerHTML = "Connecting..."
      }
    }, false)
  } else {
    console.log("Your browser doesn't support SSE")
  }
  </script>
</head>
<body>
  <h1>SSE: <span id="state"></span></h1>
  <h3>Data: <span id="data"></span></h3>
</body>
</html>

In your browser, open localhost:3000 and watch the SSE countdown.

Brent Washburne
  • 12,904
  • 4
  • 60
  • 82
  • 3
    Can you please help me, I am doing same thing as you have mentioned but my data is getting sent all at once at `res.end()`, I have even tried `res.flush()` after every `res.write()` but nothing is working. My question is here https://stackoverflow.com/questions/61412538/ndoejs-sse-res-write-not-sending-data-in-streams – Sudhanshu Gaur Apr 25 '20 at 19:21
16

I'm adding a simple implementation of SSE here. It's just one Node.js file.

You can have a look at the result here: https://glossy-ox.glitch.me/

const http = require('http');
const port = process.env.PORT || 3000;

const server = http.createServer((req, res) => {
  // Server-sent events endpoint
  if (req.url === '/events') {
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      ...(req.httpVersionMajor === 1 && { 'Connection': 'keep-alive' })
    });

    const refreshRate = 1000; // in milliseconds
    return setInterval(() => {
      const id = Date.now();
      const data = `Hello World ${id}`;
      const message =
        `retry: ${refreshRate}\nid:${id}\ndata: ${data}\n\n`;
      res.write(message);
    }, refreshRate);
  }

  // Client side
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end(`
    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title>SSE</title>
      </head>
      <body>
        <pre id="log"></pre>
      </body>
      <script>
        var eventSource = new EventSource('/events');
        eventSource.onmessage = function(event) {
          document.getElementById('log').innerHTML += event.data + '<br>';
        };
      </script>
    </html>
  `);
});

server.listen(port);

server.on('error', (err) => {
  console.log(err);
  process.exit(1);
});

server.on('listening', () => {
  console.log(`Listening on port ${port}`);
});
codeKonami
  • 910
  • 9
  • 14
  • 3
    This is a nice answer because it does use any 3rd party dependencies :) However I want to point out that `res.end(message)` should be replaced with `setInterval(() => res.write(message), 1000)` because the connection should not be ended, the same connection should be used all the time but your current code is causing the client to re-connect and create a new connection every 1 second – Karlis Bikis Mar 26 '20 at 20:39
  • 1
    Thanks @KarlisBikis for your comment. You are absolutely right. I've added your code proposition to my post. Thanks! – codeKonami Apr 02 '20 at 14:40
  • 2
    To note `'Connection': 'keep-alive'` can't be used for HTTP/2. Checking `res.httpVersionMajor` before setting the header would be required if using HTTP/2. Also, you should send a keep alive packet every 15 seconds or so (though your example sends a test packet every second). An empty comment line `:\n` would do fine. – ShortFuse Jan 28 '22 at 17:00
  • Thanks @ShortFuse for your comment. I've added a check for the HTTP/2. I didn't understand the second part of your comment though. Could you suggest some modification perhaps? Thanks – codeKonami Feb 01 '22 at 00:10
  • Sure. `Retry` is time the *client* waits to reconnect on connection drop. Spec says a "few seconds". To avoid connection drops from idle, spec suggests "include a comment line every 15 seconds or so." Based on that, all SSE should have something like `const keepAliveInterval = setInterval(() => res.write(':\n'), 15000);` Also, you should teardown on connection close to avoid a memory leak. `res.once('close', () => clearInterval(keepAliveInterval));` Last, use `no-store` instead of `no-cache` (also spec). https://html.spec.whatwg.org/multipage/server-sent-events.html – ShortFuse Feb 01 '22 at 15:10
  • 1
    In practice, I also suggest `res` be stored in a Set or Array. If want to broadcast events to a group (like `socket.io` does) you'd iterate and send. On the `close` event, remove it from the set. But this sample is well done with zero dependencies to boot. Good job :) – ShortFuse Feb 01 '22 at 15:16
  • 1
    Thanks for the clarification @ShortFuse. To be honest I've never used SSE on a production environment yet, but if I do, your messages will help me to build something more robust :) – codeKonami Feb 01 '22 at 15:52
  • Excellent example with plain Node.js, I did something similar and two points to emphasize: If using behind Nginx, would also suggest adding the [X-Accel-Buffering](https://stackoverflow.com/a/33414096/5362795) header, set to `no`, otherwise it likely won't work. And as mentioned already, the response object must be the same one used by the client to subscribe, so they need to be stored if the same event is to be sent to a group, or if a different implementation allows `res` to be overwritten. – Nagev Mar 23 '22 at 17:43
7

If you're using express this is the easiest way https://www.npmjs.com/package/express-sse

on BE:

const SSE = require('express-sse');

const sse = new SSE();

...

app.get('/sse', sse.init);

...

sse.send('message', 'event-name');

on FE:

const EventSource = require('eventsource');

const es = new EventSource('http://localhost:3000/sse');

es.addEventListener('event-name', function (message) {
  console.log('message:', message)
});
Yurii Miniv
  • 71
  • 1
  • 1
3

I found SSE implementation in node.js.

Github link: https://github.com/einaros/sse.js

NPM module:https://www.npmjs.com/package/sse

Will above link helps you ?

Gurucharan M K
  • 879
  • 1
  • 9
  • 7
2
**client.js**

var eventSource = new EventSource("/route/events");
eventSource.addEventListner("ping", function(e){log(e.data)});

//if no events specified
eventSource.addEventListner("message", function(e){log(e.data)});

**server.js**

http.createServer((req, res)=>{

    if(req.url.indexOf("/route/events")>=){

      res.setHeader('Connection', 'keep-alive');

      res.setHeader("Cache-Control", "no-cache");

      res.setHeader("Content-Type", "text/event-stream");

      let event = "event: ping";

      let id = `id: ${Date.now()}`;

      let data = {
         message:`hello @${new Date().toString()}`
      }

      data = "data: "+JSON.stringify(data);

      res.end(`${event}\n${id}\n${data}\n\n`);
   }
}).listen(PORT)
Manish Kumar
  • 538
  • 1
  • 7
  • 13
0

After looking at the other answers I finally got this working, but what I ended up having to do was a little different.

[package.json] Use express-sse:

The exact version of express-sse is very important. The latest tries to use res.flush(), but fails and crashes the http server.

"express-sse": "0.5.1",

[Terminal] Install express-sse:

npm install

[app.js] Use the router:

app.use(app.baseUri, require('./lib/server-sent-events').router);

[server-sent-events.js] Create sse library:

The call to pause() is the equivalent of flush(), which was removed from express. It ensures you'll keep getting messages as they are sent.

var express = require('express');
const SSE = require('express-sse');
const sse = new SSE();

var router = express.Router();
router.get('/sse', sse.init)
module.exports = {
    send,
    router
};


async function send(message) {
    sse.send(message.toProperCase(), 'message');
    await pause();
}

function pause() {
    return new Promise((resolve, reject) => {
       setImmediate(resolve)
    })
 }

[your-router.js] Use the sse library and call send:

var express = require('express');
var serverSentEvents = require('../lib/server-sent-events');

var router = express.Router();
router.get('/somepath', yourhandler);
module.exports = router;

async function yourhandler (req, res, next) {
    await serverSentEvents.send('hello sse!'); // <<<<<
}

[your-client-side.js] Receive the sse updates:

I recommend you keep the event.data.replace(/"/g,'') because express-sse tacks on enclosing quotes and we don't want those.

const eventSource = new EventSource('http://yourserver/sse');

eventSource.onmessage = function(event) {
    document.getElementById("result").innerHTML = event.data.replace(/"/g,'') + '...';
  };
toddmo
  • 20,682
  • 14
  • 97
  • 107
-42

You should be able to do such a thing using Socket.io. First, you will need to install it with npm install socket.io. From there, in your code you will want to have var io = require(socket.io);

You can see more in-depth examples given by Socket.IO

You could use something like this on the server:

var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('../..')(server);
var port = process.env.PORT || 3000;

server.listen(port, function () {
  console.log('Server listening at port ' + port);
});

app.use(express.static(__dirname + '/public'));

io.on('connection', function (socket) {
    socket.emit('EVENT_NAME', {data});
});

And something like this on the client:

<script src="socket_src_file_path_here"></script>
<script>
  var socket = io('http://localhost');
  socket.on('EVENT_NAME', function (data) {
    console.log(data);
    //Do whatever you want with the data on the client
  });
</script>
  • 45
    I'm absolutely **against** this approach! I'm using Socket.IO for a long time now, and I can say for sure that there are a lot of pains that you will start to fight as time goes by. Server Side Events are **unidirectional**, and sockets are **bidirectional**. One is request-response type, and the other is long-type of connection. The difference is huge and you absolutely don't need sockets - you will have to deal with reconnection, spam protection, server going down, perhaps clustering. Go with simple SSE, it's already implemented in browsers! See other answers for that! – Andrey Popov May 20 '16 at 06:59
  • 38
    The OP asked for SSE, not WebSockets. – krulik Mar 19 '17 at 13:38