Building a real-time voting app using ReactJS and Ably

Tags: ably, react, javascript, websockets
Published: 2020-12-01 17:50:23

If you wish to try out the final outcome check out the [live demo](https://realtime-voting-react-ably.herokuapp.com/).

Realtime web technologies have been around for over 10 years, but it's only relatively recently that we've started seeing them used in our day-to-day applications. This is not only because the realtime technology is better understood, has become more mature, and standardized, but also, because the users have been exposed to the amazing capabilities of the realtime web and as a result, they are now demanding those type of experiences in the apps they use.

Realtime messaging has a number of common use cases already and is constantly evolving to cater to new innovative applications. Simple use cases are things like displaying data, statistics, notifications, and news as soon as it becomes available.

[Ably](https://www.ably.io/) is an excellent realtime messaging platform that makes it easy to add realtime functionality to our applications. For our app, we’ll use [Ably’s realtime](https://www.ably.io/documentation) library which lets us connect to the platform over [WebSockets](https://www.ably.io/topic/websockets). 

##What will we build?

We will build a real-time voting app where users can cast their votes for their favorite football team and can see the real-time stats of those votes. 

###Prerequisites:
- Knowledge of JavaScript, ReactJS.
- Make sure [Node and NPM are installed](https://nodejs.org/en/download/) on your system.


##What tools will we use?

**1. Ably** 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xnfydw98f9he3j13qcp2.png)

Ably is an excellent realtime messaging platform that makes it easy to add realtime functionality to our applications. It comes with both [Realtime](https://www.ably.io/documentation/realtime) and [REST](https://www.ably.io/documentation/rest.usage) libraries to be used in accordance with the use case. 
For our app, we’ll use [Ably’s realtime](https://www.ably.io/documentation/realtime) library which lets us connect to the platform over [WebSockets](https://www.ably.io/topic/websockets).

**2. Chart.js**

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/690gs4bymt4huo80ooth.png)

[Chart.js](https://www.chartjs.org/) is a library that lets us easily include beautiful graphs that represent either static or dynamically changing data. We’ll use Chart.js to show the votes cast by our users.

**3. ReactJS** 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7l5yx15mghw1j7xrrs7r.png)

ReactJS is a JavaScript library used in web development to build interactive elements on websites.  React also allows us to create reusable UI components. The main purpose of React is to be fast, scalable, and simple. It works only on user interfaces in the application. This corresponds to the view in the MVC template.

##Application Walkthrough:
**Main** 
 The page has one button "Voting App",this will route to 
 Voting Interface and Dashboard. "Learn how to build this 
 Voting App" links back to this article.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ustzf56jcqbsg6hnpc5j.png)

**Voting Interface**
This screen allows users to vote for their favorite football team by clicking on a card.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pvft6ca4858moxh9ccsk.png)

**Voting Dashboard**
The dashboard shows the total number of votes for each team in realtime.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rbg32urkqrnccv07klrl.png)

#Let's Get Started.

###Set up an Ably account.

In order to run these tutorials locally, you will need an [Ably API key](https://support.ably.com/support/solutions/articles/3000030502-setting-up-and-managing-api-keys). If you are not already signed up, you should [sign up now for a free Ably account](https://www.ably.io/signup). 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rwvi2hlppghckyk7489l.png)

Once you signup, you will be asked ‘What do you want to do?’. 
Select ‘Build realtime apps’ and click on ‘Complete signup’.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4rn5ufeqltj0gvz1zg0t.png)

Once you are done, you should have a free account with a private key. You will see an ‘API key’ on your account dashboard, this is of importance to us as we’ll use it later in the tutorial to connect to [Ably](https://www.ably.io/) using the [Token Authentication](https://www.ably.io/documentation/core-features/authentication#token-authentication) scheme.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/v4q7h6if20uq2a6vmzpq.png)

Click on ‘Dashboard’ on the top-right corner.
 
If you've successfully arrived on your dashboard, you should see a blue button labeled 'Create New App'. Create an Ably app using the button, then enter a project name for the project/app, as shown below.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6nd1nqy6ob0tzddjgx0c.png)

###Create a React App
Set up a folder and open the terminal, We’ll start by creating a new React app.  We’ll use [create-react-app](https://github.com/facebook/create-react-app) to do this. Let’s create the app from the terminal by running:

```
npx create-react-app voting-app
```
Here `voting-app` represents the name of the project.
We can now run the project locally from the terminal by running:


```
cd voting-app && npm start
```

If your browser does not open automatically, navigate manually to http://localhost:3000 to see this app.

###Dependencies
Let’s install the dependencies by running the following command in the terminal:
```
npm install --save ably react-chartjs-2 chart.js react-router-dom
```

‘ably’ is the package offered by Ably to use it on our client-side.

‘react-chartjs-2 chart.js’ are for using chart.js in our app.

‘react-router-dom’ for using routing in our app.

###Base Styles
You can refer to the styling from here.
 Main  - [App.css](https://github.com/akashgupta1116/realtime-voting-reactjs/blob/master/voting-app/src/App.css) . 
 Voting Interface - [voting.css](https://github.com/akashgupta1116/realtime-voting-reactjs/blob/master/voting-app/src/components/voting.css)
 
These are basic styles to make our app look more presentable.

###Handling Routes
To handle the routing for different views, modify App.js file as below:

```react
import React from 'react';
import './App.css';
import Voting from './components/Voting';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Main from './components/Main';
function App() {
 return (
   <div className="App">
     <Router>
         <Switch>
           <Route exact path="/voting">
              <Voting/>
           </Route>
           <Route exact path='/'>
              <Main/>
           </Route>
         </Switch>
     </Router>
   </div>
 );
}

export default App;
```
Here we are using [react-router](https://reactrouter.com/).

In the Route component, we need to pass the ‘path’ prop which takes in the URL of the page. The Route will render its children for the specified path. For example, when we go to ‘/voting’  the Voting component would be rendered.

We have not created the `Voting` and `Main` component, so this will fail to build. We will make these components in the next section.

###Creating Components

Now,  Go to `/voting-app/src` and create a new folder called ‘components’. This folder will contain all our React components.
Create a new file Voting.js inside the  ‘components’ folder and add the following code to it.

```react
class Voting extends Component {
...
 clickHandler = (card) => {
   if (this.state.flipped) {
     return;
   }
   this.setState({
     flipped: card,
   });
 };
...
}

export default Voting;
```
We have added an event-listener i.e. ‘clickHandler’ which gets triggered on clicking any card. The ‘clickHandler’ first check if any card is already flipped, if not, then the clicked card will be flipped.

Now create a new file Dashboard.js in the ‘components’ folder and add the following code to it.

```react
import React, { Component } from "react";
import * as Ably from "ably";
import { Doughnut } from "react-chartjs-2";

class Dashboard extends Component {
 state = {
   votes: {
     barcelona: 0,
     realMadrid: 0,
     juventus: 0,
   },
 };

 render() {
   const data = {
     labels: ["Barcelona", "Real Madrid", "Juventus"],
     datasets: [
       {
         barPercentage: 1,
         backgroundColor: ["#FF6384", "#4BC0C0", "#FFCE56"],
         data: [
           this.state.votes.barcelona,
           this.state.votes.realMadrid,
           this.state.votes.juventus,
         ],
       },
     ],
   };

   const options = {
     title: {
       display: true,
       text: "Voting Dashboard",
       fontSize: 25,
       fontColor: "#CB0F33",
     },
     layout: {
       padding: {
         top: 50,
       }
     }
   };
   return <Doughnut data={data} options={options} />;
 }
}

export default Dashboard;
```
Here, we have utilized the [Chart.js](https://www.chartjs.org/) library to render a Doughnut chart based on the number of votes each team gets.

The data prop contains the votes and the options prop contains the styling config for the Doughnut chart. If you want to learn more about these options do checkout [Chart.js documentation](https://www.chartjs.org/docs/latest/).

Now add the Dashboard Component to the Voting Component; 
```react
import Dashboard from './Dashboard';
...
class Voting extends Component {
...
 render(){
...
  <Dashboard/>
...
 }
}
export default Voting; 
```


###Token Authentication with Ably
Now, let’s instantiate the [Ably realtime library](https://www.ably.io/documentation/realtime) as well as the channels that we’ll use to share realtime data. 

The two schemes supported by Ably are [Basic Authentication](https://www.ably.io/documentation/core-features/authentication#basic-authentication), which uses your private [API key](https://support.ably.com/solution/articles/3000030054-what-is-an-app-api-key), and [Token Authentication](https://www.ably.io/documentation/core-features/authentication#token-authentication), which uses short-lived tokens for access which are periodically renewed.

Before a client connects to Ably, it will check if it has suitable credentials to authenticate with Ably. 

Client-side devices should generally be considered untrusted, and as such, it is important that you minimize the impact of any credentials being compromised on those devices. Token authentication achieves this by having a trusted device, such as one of your own servers, possessing an API key [configured via the dashboard](https://support.ably.com/support/solutions/articles/3000030502-setting-up-and-managing-api-keys).

As you’ll be using the recommended [token authentication scheme](https://www.ably.io/documentation/core-features/authentication#token-authentication) in the client for this demo, when the client starts up and attempts to connect to Ably, it will request a token immediately so that it can then authenticate with Ably. Hence we are going to set up a server for the same. 


###Setting up the server
Using Ably Realtime client libraries, an Ably TokenRequest is generated from our servers and handed to our Voting App. The Voting App then uses that Ably TokenRequest to request an Ably Token from Ably and subsequently authenticate using that [Ably Token](https://www.ably.io/documentation/core-features/authentication#ably-tokens). Ably TokenRequests cannot be tampered with due to being signed, must be used soon after creation, and can only be used once.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bgegxv6qh6mviy2328f5.png)

[Express.js](https://expressjs.com/) is a very popular and simple web framework for Node.js. You’ll need to get this setup:
Go to your root folder (in our case ./real-time-voting-app) add the `express` NPM module and create a `package.json` file:
```json
{
 "name": "voting-server",
 "version": "1.0.0",
 "description": "",
 "main": "server.js",
 "dependencies": {
   "ably": "^1.2.2",
   "concurrently": "^5.3.0",
   "cors": "^2.8.5",
   "dotenv": "^8.2.0",
   "express": "^4.17.1"
 },
 "devDependencies": {},
 "scripts": {
   "client-install": "npm install --prefix voting-app",
   "start": "node server.js",
   "start-client": "npm start --prefix voting-app",
   "dev": "concurrently \"npm start\" \"npm run start-client\""
 },
 "author": "",
 "license": "ISC"
}
```

Then you need to set up a vanilla HTTP Express.js server in `server.js`:

```js
const express = require("express");
const envConfig = require("dotenv").config();
const Ably = require("ably");
const path = require('path');
const cors = require("cors");

const app = express();
app.use(cors());
const realtime = Ably.Realtime({
 key: process.env.ABLY_API_KEY,
});

app.use(express.static(path.join(__dirname, 'voting-app/build')));

const listener = app.listen(process.env.PORT, () => {
 console.log("App is listening on port " + listener.address().port);
});
```

Create `.env` file in your root folder and add your Ably API key 

.env 
```
PORT = 3001
ABLY_API_KEY = <YOUR_ABLY_API_KEY>
```
Our folder structure will look like this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cbxeklutk5rmmhpi5ye6.png)

If you would like to try running the server now, you can do so with npm install and `node server.js`. Once running, open your browser to http://localhost:3001/ and you should see the text **“App is listening on port: 3001”**.

**Tokens**
All clients authenticating with Ably must use either an API key or a token. Tokens are obtained by sending a [TokenRequest](https://www.ably.io/documentation/realtime/authentication#token-request) containing the required token spec to the Ably service. The token may include a set of capabilities (permissions such as subscribe access to a specific channel), and identity (such as the logged-in user’s unique ID), or a TTL (the time before the token expires).

**Token Requests**
Token requests, unlike tokens, are created and signed by your server without having to communicate with Ably. A token request is simply a JSON object that contains a pre-authorization from your server for a client, effectively stating “Ably, with this signed token, I authorize you to issue a token according to the permissions, ID, and TTL specified, to whoever hands this to you”. Ably is then able to inspect the signature to ensure that the token request is indeed from your server and signed with your private API key. Ably will then issue a token to the client requesting the token. Ably ensures that token requests can only be used soon after creation and can only be used once.

By adding the following route to your Express.js server, it will be ready to service clients wanting to authenticate with Ably.

Add the following code to your server.js file:

```js
app.get("/publish", (request, response) => {
 
 const tokenParams = {
   capability: '{"*":["publish"]}',
 };
 realTimeAuth(tokenParams, response);
});

app.get("/subscribe", (request, response) => {

 const tokenParams = {
   capability: '{"*":["subscribe"]}',
 };
 realTimeAuth(tokenParams, response);
});

const realTimeAuth = (tokenParams, response) => {
 realtime.auth.createTokenRequest(tokenParams, function (err, tokenRequest) {
   if (err) {
     response
       .status(500)
       .send("Error requesting token: " + JSON.stringify(err));
   } else {
     // return the token request to the front-end client
     response.json(tokenRequest);
   }
 });
};
```

Here, we have created two URLs ‘publish’ and ‘subscribe’ to provide the desired [capability](https://www.ably.io/documentation/core-features/authentication#capabilities-explained-intersection). If an API key must be shared with a third party, then it is recommended that [the principle of least privilege](http://en.wikipedia.org/wiki/Principle_of_least_privilege) is considered, assigning only the capabilities needed by that third party. Thus, any Ably requests authenticated using that API key or Ably-compatible tokens associated with that API key, will be restricted to the capabilities assigned to the API key.

###Publishing and Subscribing at Client side:
In /voting-app/src/components/Voting.js add the following code inside clickHandler.
Then in the ‘clickhandler’ function, we add a publishing function where we basically publish users' votes to the channel.
```react
let realTime = null;
let myVotingChannel = null;
class Voting extends from Component {
…
 componentDidMount(){
  
   realTime = new Ably.Realtime({ authUrl: "/publish" });
   realTime.connection.once("connected", () => {
     // create the channel object
     myVotingChannel = realTime.channels.get("Voting-App");
   });
 }

 clickhandler(card){
…
  myVotingChannel.publish("vote", card.value, (err) => {
       console.log("err", err);
     });
…
 }

```
For cleaning of connections add the following code.

```react
componentWillUnmount(){
  realTime.connection.off()
}
```

In /voting-app/src/components/Dashboard.js add the following code:
```react
let realTime = null;
let myVotingChannel = null;
class Dashboard extends from Component {
…

componentDidMount() {
   realTime = new Ably.Realtime({ authUrl: "/subscribe" });
   realTime.connection.once("connected", () => {
     // create the channel object
     const myVotingChannel = realTime.channels.get("Voting-App");
     myVotingChannel.subscribe("vote", (msg) => {
       this.setState({
         votes: {
           ...this.state.votes,
           [msg.data]: this.state.votes[msg.data] + 1,
         },
       });
     });
   });
 }
```
For cleaning of connection and subscription in Dashboard component add following code:

```react
componentWillUnmount(){
 myVotingChannel.unsubscribe()
  realTime.connection.off()
}
```


###Bringing it all together
Run:
```
npm run dev
```
I will start the development server and navigate to http://localhost:3000 to check it out.
 
And that is it.
If you miss any of the steps, you can [find the full source code for this voting app on GitHub](https://github.com/akashgupta1116/realtime-voting-reactjs).

You can compare your app with [live demo](https://realtime-voting-react-ably.herokuapp.com/).

###Conclusion
We have successfully achieved two things in this tutorial:
 1. Get introduced to building web applications using ReactJS.
 2. Explore the realtime functionality offered by [Ably](https://www.ably.io/)
If you would like to find out more about how channels, [Pub/Sub](https://www.ably.io/topic/pub-sub) works, see the [Realtime channels](https://www.ably.io/documentation/realtime/channels-messages) & [messages documentation](https://www.ably.io/documentation/realtime/channels-messages) or better still learn more about the complete set of [Ably features](https://www.ably.io/features).