DEV Community

Cover image for Build a Real-Time Application with Amazon ElastiCache for Redis
Gilad David Maayan
Gilad David Maayan

Posted on

Build a Real-Time Application with Amazon ElastiCache for Redis

In this article, we’ll walk you through the steps required to combine Socket.io and Redis in a Nodejs backend and hook it up with a React frontend. We’ll then see the steps to deploy the application on a suitable platform. So, what will we create? A basic chat application with the help of Redis. The user can set their usernames and then send messages.

This model is well suited for a number of applications including games, chat, trading, etc. This is article is not exclusive to React and the fundamental principles should be easy to translate to other frameworks like Angular and Vue. However, you should be familiar with the concepts of React, Express and Redis to get started.

The Structure

To build a real-time application like that of a chat application, we’ll need to set up a channel through which the client can send messages and the messages are shared with other participants in the group. We’ll be using Websockets to manage the persistent TCP communication between the client and the server. However, the communication will be implemented using the publish-subscribe pattern (PubSub).

Here, a message is sent to a centralized topic channel. Interested parties can subscribe to this channel to be notified of updates. This pattern decouples the publisher and subscribers, so that the set of subscribers can grow or shrink without the knowledge of the publisher. Redis is a fast, open-source, in-memory data store and cache that features PubSub support.

Our server will using be an Express server running on a Node environment. Although you might be familiar running Express server on a persistent DB like MongoDB, DynamoDB for AWS or MySQL, cache databases are somewhat different. For deployment, you can set it up using Amazon ElastiCache for Redis or use something like Redis Enterprise Cloud for AWS. We won’t be covering that in this tutorial.

Setting up the basics

You can find the source code for this chat application on GitHub. You will need to run npm install and then start the server and the front end app.

Before we start, we’re assuming that you have the fundamentals like npm and node installed.

If not already done so, you will also need to install create-react-app before we start. This can be done by executing the following command:

 npm --global i create-react-app

Once done, you can now generate the app that will be used to experiment with sockets by executing:

create-react-app socket-redis-chat

After generating the app, open the folder using your preferred text editor. To run the project, you need to run ‘npm start’ from within the app folder.

In this article, we will run the client as well as the server within the same codebase. While this would probably not be done in a production app, it’ll be easier to explain here.

Socket.io and Redis on the Server

To create a websocket service, simply navigate to a terminal in your app folder and install socket.io:

npm i --save socket.io redis socket.io-redis

By running socket.io with the socket.io-redis adapter you can run multiple socket.io instances in different processes or servers that can all broadcast and emit events to and from each other. Once socket.io has been installed, you need to create a file called ‘server.js’ within the root folder of the app that was generated earlier.

Within this file, type the code below to import and start constructing the socket:

const io = require('socket.io')();
var redis = require('redis');
var redis2 = require('socket.io-redis');

var pub = redis.createClient();
var sub = redis.createClient();
io.adapter(redis2({ host: 'localhost', port: 6379 }));

To begin constructing the socket, we will be using the io variable. We’ll also initialize a PubSub architecture and set the io adapter to use the localhost.

Sockets are essentially a set of long duplex channels between the server and the client. Therefore, the critical part on the server is to successfully handle a connection from a client. This will allow you to publish events to the corresponding client. This can be done via the following code:

io.on('connection', (client) => {
  io.sockets.on('connection', function (socket) {
    socket.removeAllListeners()
    // here you can start emitting events to the client 
  })
});

The socket.removeAllListereners() command is used to remove any existing sockets when starting up your application. Now you will need to inform socket.io to begin listening for clients:

const port = 8000;
io.listen(port);
console.log('listening on port ', port);

You can now navigate to your terminal and start the server by executing ‘node server’. You should see the following message once it starts up – ‘listening on port 8000’

At this stage, the socket is not really engaged. While you do have access to the client sockets, nothing is being transmitted to them yet. However, since access has been granted to a connected client, you will be able to respond to events that are being transmitted from the client. You can imagine it as being akin to an event handler at the server side with respect to a specific event from a specific client.

The first objective is to have the server respond to setUsername requests from the client. The service should let the user know that they are online and the client should be able to send data to the server socket. You can modify your code to add the following:

socket.on("setUsername", function (data) {

       console.log("Got 'setUsername' from client, " + JSON.stringify(data));
       var reply = JSON.stringify({
               method: 'message',
               sendType: 'sendToSelf',
               data: `${data.user} is now online`
           });    


       socket.join("work");
       pub.publish("work", reply);
   });

Now, the user is able to set a username and you can use socket.join to add the client to a particular room. We’re going to call it “work”. pub.publish() posts the JSON that we created to the “work” channel.

Next, we’re going to write the logic for sending messages.

   socket.on("sendMessage", function (data) {
   console.log("Got 'sendMessage' from client , " + JSON.stringify(data));
       var reply = JSON.stringify({
               method: 'message',
               sendType: 'sendToAllClientsInRoom',
               data: data.user + ":" + data.msg
           });


       pub.publish("work",reply);

   });

The procedure is quite similar. We’ll just convert it into JSON and publish it into the channel.

So far, we’ve covered two cases. The user:
Joins the channel
Sends a message

Next, we need to disconnect the user when he quits. For that, you can use this code:

   socket.on('disconnect', function () {
       sub.quit();
       pub.publish("chatting","User is disconnected :" + socket.id);
   });

Your socket should now have started and begun to look for clients. As a client connects, you will have a closure where you will be able to handle events from a specific client. You should also be able to handle specific events like setUsername that is being transmitted from the client.

So, how do we broadcast the message? We’ll use the Redis subscriber pattern to cover that.

sub.on("message", function (channel, data) {
 data = JSON.parse(data);
 console.log("Inside Redis_Sub: data from channel " + channel + ": " + (data.sendType));
 if (parseInt("sendToSelf".localeCompare(data.sendType)) === 0) {
      io.emit(data.method, data.data);
 }else if (parseInt("sendToAllConnectedClients".localeCompare(data.sendType)) === 0) {
      io.sockets.emit(data.method, data.data);
 }else if (parseInt("sendToAllClientsInRoom".localeCompare(data.sendType)) === 0) {
     io.sockets.in(channel).emit(data.method, data.data);
 }      

});

Let’s take this step by step. The subscriber process receives the messages passed onto it when you call pub.publish(). We create a function that accepts two parameters, the first one is the channel and the second one is called data. The data.sendType holds the details regarding the audience that we want the message to be broadcasted. This completes the socket.io set up for the server side. Now it is time to move to the client.

Ensure that the latest server is running by executing ‘node server’ in a terminal. In case terminal was already active when the last change was made, simply restarting it should suffice.

Socket.io on the Client

Earlier in this guide, we started the React app by executing ‘npm start’ on the command line. Therefore, now you should be able to view your code, modify it, and see the browser reload your app with the changes you make.

To begin, you need to start up the client socket code that will be communicating with the server side socket. For simplicity, I am going to put all the API calls into a separate file. To accomplish this, create a file in the src folder and name it api.js. In this file, we’ll create socket functions to complete the communication cycle.

A good place to begin is by defining the functions and having it exported from the module:



function setUsername(username) {
 /* To be completed */ 
}

function setMessage(username, message) {
 /* To be completed */ 
}

function subscribeToMessages(cb) {
 socket.on('message', function(message){
   /* To be completed */ 

}) ;
}

function disconnectSocket(cb) {
 socket.on('disconnect', function(message) {
  /* To be completed */ 

 })
}


export { disconnectSocket, setMessage, setUsername, subscribeToMessages };


What we’re basically doing here is creating functions corresponding to the socket functions that we created earlier.

Since we need to communicate with the server socket, we need to install the client library, socket.io. This can be installed on the command line with the help of npm – npm I -- save socket.io-client.

Now that this has been installed, it can be imported. As we are running a code on the client side, we can utilize the syntax for the ES6 module. The client side code will be transpiled with Babel and Webpack.

The socket can also be constructed by recalling the primary export function from the module, socket.io-client, and assigning a port. In this case, the port is 8000:

import openSocket from 'socket.io-client';
const socket = openSocket('http://localhost:8000');

Next, let’s just fill in the code for the socket functions.

function setUsername(username) {
 console.log(username);
 socket.emit('setUsername',{user:username});
}


function setMessage(username, message) {
 const msg =   {user:username, msg: message};
 socket.emit('sendMessage',msg);
}

function subscribeToMessages(cb) {
 socket.on('message', function(message){
   console.log(message);
   //alert('received msg=' + message);
  cb(null, message)
}) ;
}

function disconnectSocket(cb) {
 socket.on('disconnect', function(message) {
   cb(null, message)
 })
}

A point to note is that the subscribeToMessages event needs to be subscribed to on the socket. This needs to be completed before the events are transmitted. We’ll have a look at that in the next step.

Using the events in a React component

You should now have an api.js file on the client side. This will export functions that can be called in order to subscribe and emit events. In the next phase, we will go over how to utilize this function in a React component to be able to accept inputs and then render the messages.

To begin, import the API that was created earlier on the top of the App.js file that was generated using the create-react-app.

import { disconnectSocket, setMessage, setUsername, subscribeToMessages } from './test';

Next, let us define the state for our application:

  state = {
    username: '',
    room: '',
    message: '',
    messageList: []
  };


Once completed, it is time to add a constructor to the file component. Within this constructor, you can call the subscribetoMessages function that we receive from the API file.

Each time that an event is received, we can set a value, known as a “message”, using the value that was received from the server. We will then append the message to the existing message list as follows:

  constructor(props) {
    super(props);

    subscribeToMessages((err, message) => {
      console.log(message);
      this.setState({
      messageList: [...this.state.messageList, message]
    }

  )})
  }

Finally, add the methods to handle the events - setUsername, setMessage and disconnectSocket (when component unmounts).

  componentWillUnmount() {
    disconnectSocket((err, message) => {
      console.log(message)
    });
  }

  submitChat() {

    if(this.state.username   && this.state.message) {
      setMessage(this.state.username, this.state.message);
    }
  }

  submitUsername() {
    if(this.state.username!="") {
      setUsername(this.state.username);
    }

You should be able to view the events in your browser as they come in from the server and are rendered within your React component.

Summary

Phew! We’ve covered a lot of ground. Although the code here is very basic, it works well enough and you can scale it based on your needs to handle much more complexity without facing many obstacles. We have just about touched the tip of the iceberg when it comes to developing real-time apps using Socket.io and React.

Top comments (0)