0% found this document useful (0 votes)
109 views

Real Time Push Messages From Server - Server Sent Event in Node - Js With Express - JS, Redis - by Ajinkya Rajput - Medium

This document discusses implementing server-sent events (SSE) in a Node.js application using Express and Redis. SSE allows a client to receive automatic updates from a server via an HTTP connection. The document covers: 1. Setting up a basic SSE implementation in Node.js to publish messages to connected clients. 2. Enhancing it to support multiple simultaneous user connections by storing all connection objects in memory. 3. Adding user authentication and rules for publishing only to certain users based on roles or user IDs.

Uploaded by

Andreas Rutjens
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
109 views

Real Time Push Messages From Server - Server Sent Event in Node - Js With Express - JS, Redis - by Ajinkya Rajput - Medium

This document discusses implementing server-sent events (SSE) in a Node.js application using Express and Redis. SSE allows a client to receive automatic updates from a server via an HTTP connection. The document covers: 1. Setting up a basic SSE implementation in Node.js to publish messages to connected clients. 2. Enhancing it to support multiple simultaneous user connections by storing all connection objects in memory. 3. Adding user authentication and rules for publishing only to certain users based on roles or user IDs.

Uploaded by

Andreas Rutjens
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

Open in app Get started

Ajinkya Rajput Follow

May 9 · 6 min read · Listen

Save

Real Time push messages from Server -Server


Sent Event in Node.js with Express.js, Redis
In this blog, we are going to understand Server Sent Events(SSE) and implementation
in node.js with Express and Redis.

SSE is a server push technology enabling a client to receive automatic updates from a
server via an HTTP connection.

When does SSE come into picture?

Traditionally, To get updates from the server with http, we have to ask the server if
there are any updates available(ref. image 1). This adds load on the server, not real
time. As we are heading towards an event-driven, SSE got introduced with the same
idea. The idea is simple: a frontend/a browser can subscribe to a stream of events
generated by a server, receiving updates whenever a new event occurs(ref. image 2).
Now, the frontend doesn’t have to ask for updates, real time, and low load on the
server. Once the frontend/client opens a connection with the server, it will not get
close until the frontend/client will explicitly close the connection.

106 1
Open in app Get started

Examples where SSE can be used:

1. Changes in Stock price.

2. Notify me, Alert when product is available like in online shopping portal.

3. News feeds

4. YouTube Alerting system

5. Facebook notification Alert system

6. Live sport updates

Pros:

SSEs are sent over traditional HTTP. That means they do not require a special protocol
or server implementation to get working.

Simpler protocol

Cons:

Data transmission supports only UTF-8(No binary support).

Maximum open connections limit.

Nodejs Implementation(Test Cases)

In this tutorial, we will cover all test cases at backend which we’ll be required to add
SSE in our project:

1. Simple Publish

Open in app Get started


2. Multiple User Connection

3. Right To Privacy
4. Multi Instance Server Architecture

5. Network Settings/Configuration

Before starting test cases, we should have express setup ready.

Step 1: Open terminal, change directory to the folder we have to work on this project,
install express

npm init -y

npm i express

Step 2: Setup express

1 const express = require('express');


2 const app = express();
3 const PORT = 3000;
4 app.listen(PORT, () => {
5 console.log(`Express Server listening at ${PORT}`)
6 })

index.js
hosted with ❤ by GitHub view raw

Step 3: Verify if express setup done

node index.js

1. Simple Publish

Create one api for live/stream connection. This connection will be used by the server
to push notification to the client.
In the API, once a request is received, send 3 headers to the client so the connection
Open in app Get started
will be live and store res obj which will be used to send messages.

1 var socket
2 function socketConnectionRequest(req, res, next) {
3 const headers = {
4 'Content-Type': 'text/event-stream', // To tell client, it is event stream
5 'Connection': 'keep-alive', // To tell client, not to close connection
6 };
7 res.writeHead(200, headers);
8 res.write('data: Connection Established, We\'ll now start receiving messages from the
9 socket = res
10 console.log('New connection established')
11 }
12
13 module.exports = { socketConnectionRequest }

socket.js
hosted with ❤ by GitHub view raw

1 const { socketConnectionRequest } = require('./socket')


2 app.get('/socket-connection-request', socketConnectionRequest);

index.js
hosted with ❤ by GitHub view raw
Once a connection is established between server and client, use this connection to
Open in app Get started
send messages to clients.

1 function publishMessageToConnectedSockets(data) {
2 socket.write(`data: ${data}\n`);
3 }

socket.js
hosted with ❤ by GitHub view raw

Now create an API which will send messages to connections

1 const { socketConnectionRequest, publishMessageToConnectedSockets } = require('./socket')


2 app.get('/send-message-to-client', (req, res, next) => {
3 publishMessageToConnectedSockets(`This event is triggered at ${new Date()}`)
4 res.sendStatus(200)
5 });

index.js
hosted with ❤ by GitHub view raw

Restart node js server(1st terminal) to verify its working

node index.js

Open terminal(2nd terminal) and hit sse connection api with the help of curl

curl -H Accept:text/event-stream http://localhost:3000/socket-connection-request

Open another terminal(3rd terminal) and hit send message to client api

curl http://localhost:3000/send-message-to-client
As soon as request reach to server, we’ll see `This event is triggered at` message in 2nd
Open in app Get started
terminal, we can hit this api any amount of times and we’ll received message in 2nd
terminal

2. Multiple User Connection

Above implementation does not support multiple user connections, let’s say we have
hit the connection api from the new terminal(4th terminal) and hit send message api,
2nd terminal message will stop and messages at 4th terminal will start. That means
multiple users can’t be handled as we are overriding connection objects on every new
connection request. To overcome this issue, we have to store all connection objects in
node js application memory and on publish we have to iterate all connections and send
to each. Also we have to remove connection on close/loss.

1 var sockets = []
2 function socketConnectionRequest(req, res, next) {
3 const headers = {
4 'Content-Type': 'text/event-stream', // To tell client, it is an event stream
5 'Connection': 'keep-alive', // To tell client not to close connection
6 };
7 res.writeHead(200, headers);
8
9 const socketId = Symbol(Date.now())
10
11 const socket = {
12 socketId,
13 res
13 res,
14 }
Open in app Get started
15 console.log(`New connection established:`, socketId)
16 res.write('data: Connection Established, We\'ll now start receiving messages from the
17
18 sockets.push(socket)
19 req.on('close', function () {
20 console.log(socketId, `Connection closed`)
21 sockets = sockets.filter((socket) => socket.socketId !== socketId)
22 })
23 }
24
25 function publishMessageToConnectedSockets(data) {
26 sockets.forEach((socket) => {
27 const { res } = socket
28 res.write(`data: ${data}\n\n`)
29 })
30 }

socket.js
hosted with ❤ by GitHub view raw
Open in app Get started

To verify, hit connection api from 2 terminal and hit send api from another terminal
and connection api terminal should receive messages.

Note: For simplicity purpose, we are storing in array which is not efficient but in real
application we can use different data structure as per our requirement.

3. Right To Privacy

With the above implementation, there is still an issue with privacy, i.e. let’s say a group
of 10 users called group1 belongs to role1 and another 10 called group2 belongs to
role2. While publishing, we have to publish only to users who belong to that role.

To resolve this issue, we have to make some strategy. We can achieve this by storing an
array of rules in node js memory with connection objects on the time of connection.
Rules means, roles, user id, etc. if we want to publish event

i) by role name, then it would be roles

ii) to the particular user then it would be user’s unique id

iii) combinations with above, etc

Here expectation is, the user is authenticated and the request object contains user
objects after authentication.

To create scenarios of authentication, we have added middleware to add user object in


request object. For simplicity, since we are focusing on SSE, we have not added any
authentication mechanism but in real time it would be JWT or passport.js or any other.

1 const users = [{ "id": 1, "email": "[email protected]", "roles": ["employer"] }, { "id": 2, "e


2
3 const auth = (req, res, next) => {
4 const email = req.headers['email']
5 let user = users.find(u => u.email === email)
6 if (!user) {
7 const uniqueId = Date.now()
8 user = { "id": uniqueId, "email": `@sse.com">guest${uniqueId}@sse.com`, "roles": [
9 }
9 }
10 req.user = user
Open in app Get started
11 next()
12 }
13
14 app.get('/socket-connection-request', auth, socketConnectionRequest);

index.js
hosted with ❤ by GitHub view raw

1 var sockets = []
2 function socketConnectionRequest(req, res, next) {
3 const headers = {
4 'Content-Type': 'text/event-stream', // To tell client, it is an event stream
5 'Connection': 'keep-alive', // To tell client not to close connection
6 };
7 res.writeHead(200, headers);
8
9 const socketId = Symbol(Date.now())
10 const socket = {
11 socketId,
12 res,
13 roles: req.user.roles,
14 userId: req.user.id,
15 }
16 console.log(`New connection established for user id= ${req.user.id} of connection Id =
17 res.write('data: Connection Established, We\'ll now start receiving messages from the
18 sockets.push(socket)
19 req.on('close', function () {
20 console.log(socketId, `Connection closed`)
g
21 sockets = sockets.filter((socketId) => socket.socketId !== socketId)
Open in app Get started
22 })
23 }

socket.js
hosted with ❤ by GitHub view raw

We will update our publish method something like

1 function publishMessageToConnectedSockets(data, { role, userId } = {}) {


2 sockets.forEach((socket) => {
3 if ((role && socket.roles.indexOf(role) === -1) || (userId && socket.userId !== use
4 return
5 }
6 const { res } = socket
7 res.write(`data: ${data}\n\n`)
8 })
9 }

socket.js
hosted with ❤ by GitHub view raw
Open in app Get started

New function to send events by role will look like this

1 function publishMessageToConnectedSocketsByRole(data, role) {


2 publishMessageToConnectedSockets(data, { role })
3 }

socket.js
hosted with ❤ by GitHub view raw

New function to send message to user by id which not guest

1 function publishMessageToConnectedSocketsByUser(data, userId) {


2 publishMessageToConnectedSockets(data, { userId })
3 }

socket.js
hosted with ❤ by GitHub view raw

We have to add 2 APIs. 1. Send message to the role 2. Send message to the user

1 const { socketConnectionRequest, publishMessageToConnectedSockets, publishMessageToConnecte


2 app.get('/send-message-to-role', (req, res, next) => {
3 publishMessageToConnectedSocketsByRole(`This event is triggered at ${new Date()}`, req.
4 res.sendStatus(200)
5 }); Open in app Get started
6 app.get('/send-message-to-user', (req, res, next) => {
7 publishMessageToConnectedSocketsByUser(`This event is triggered at ${new Date()}`, +req
8 res.sendStatus(200)
9 });

index.js
hosted with ❤ by GitHub view raw

Now restart node server and to make connection we have to pass email as header(here
jwt or express session may be present in real application with different header name)

For example, connection of user1

curl -H Accept:text/event-stream -H email:[email protected] http://localhost:3000/socket-


connection-request

To send message to role, e.g. employer

curl http://localhost:3000/send-message-to-role?role=employer

To send message to user id e.g 1

curl http://localhost:3000/send-message-to-user?userId=1

4. Multi Instance Server Architecture

Now we are done with all local server(Single server architecture) test cases. Let’s
consider the scenario where we have multi server architecture while deployment.
Like, we have a load balancer for routing and there are more than 1 node js server
behind the load balancer. That means, the user’s request can go to any of the node
servers. Suppose, some users have a connection with Nodejs server 1 and some with
Open in app Get started
Nodejs server 2. If a publish request came to nodejs server 1, all the users connected to
the server 1 will receive messages but users who are connected to the server 2 will not
receive messages. Ref following diagram.

We have understood the problem now. Let’s work on the solution.

One of the solutions is that we should have a communicating server which will publish
subscriptions between all Node.js servers. Redis is well known for publishing
subscriptions; we can use Redis for communicating with servers.

With Redis, all Nodejs servers will subscribe to the one common channel. Once we
receive a publish event, we’ll not publish directly to all connections(like we did in
previous steps), we’ll publish it to that redis channel. On receiving messages from the
redis channel, we’ll iterate over all connections who are connected to the node js
server from frontend and publish messages to those connections.
Open in app Get started

To achieve this, we need a redis server running locally.

we have to install redis module in our project

npm i redis

We need to redis client connection to publish and to subscribe. We will create this
connection on the start of the nodejs server.

1 const redis = require('redis');


2 const { onReceiveMessageFromRedis } = require('./socket')
3
4 const RedisConfig = {
5 host: process.env.REDIS_HOST || '127.0.0.1',
6 password: process.env.REDIS_PASSWORD,
7 port: process.env.REDIS_PORT || '6379',
8 }
9 const CHANNEL_NAME_TO_PUBLISH_SSE_MESSAGES = 'sse-messages'
10 let redisClientToPublishMessages, redisClientToSubscribeChannel
11
12 async function createConnectionAndSubscribeRedis() {
13 if (!redisClientToSubscribeChannel) {
14 redisClientToPublishMessages = redis.createClient(RedisConfig)
15 redisClientToSubscribeChannel = redisClientToPublishMessages.duplicate() // Subscr
16 await redisClientToPublishMessages.connect()
17 await redisClientToSubscribeChannel.connect()
18 }
19 Open in app Get started
20 redisClientToSubscribeChannel.on('error', function (err) {
21 console.error(err)
22 })
23
24 redisClientToSubscribeChannel.subscribe(CHANNEL_NAME_TO_PUBLISH_SSE_MESSAGES, onReceiv
25 }

redis-sse.js
hosted with ❤ by GitHub view raw

As discussed, we’ll publish a message in redis first and on receiving a message from
redis, we’ll publish it to connections, so publishMessageToConnectedSockets,
publishMessageToConnectedSocketsByRole,
publishMessageToConnectedSocketsByUser will get moved to the new file. As we are
publishing it to redis, we have to serialize data.
1 function publishMessageToConnectedSockets(data, { role, userId } = {}) {
2 Open in app GetJSON.string
redisClientToPublishMessages.publish(CHANNEL_NAME_TO_PUBLISH_SSE_MESSAGES, started

3 }
4
5 function publishMessageToConnectedSocketsByRole(data, role) {
6 publishMessageToConnectedSockets(data, { role })
7 }
8
9 function publishMessageToConnectedSocketsByUser(data, userId) {
10 publishMessageToConnectedSockets(data, { userId })
11 }

redis-sse.js
hosted with ❤ by GitHub view raw

New function we have to create in socket.js to send messages on receiving messages


from redis

1 function onReceiveMessageFromRedis(message) {
2 const { data, role, userId } = JSON.parse(message)
3 sockets.forEach((socket) => {
4 if ((role && socket.roles.indexOf(role) === -1) || (userId && socket.userId !== us
5 return
6 }
7 const { res } = socket
8 res.write(`data: ${data}\n\n`)
9 })
10 }

socket.js
hosted with ❤ by GitHub view raw
Open in app Get started

1 const { socketConnectionRequest } = require('./socket')


2 const { createConnectionAndSubscribeRedis, publishMessageToConnectedSockets, publishMessage
3 createConnectionAndSubscribeRedis()

index.js
hosted with ❤ by GitHub view raw

Let’s verify all test cases, we did in previous steps

5. Network Settings/Configuration

Since SSE needs to be real time without buffering, there might be an issue where the
frontend will not be receiving any messages and when the connection is closed, all the
messages will arrive at once. To solve this issue you will need to check the
configuration of the key networking elements. Buffer streams should be disabled for
all network elements in our network path.

You can find the code source on the GitHub repository.


Open in app Get started

About Help Terms Privacy

Get the Medium app

You might also like