DEV Community

loading...

Building a real-time voting app using ReactJS and Ably

akashgupta1116 profile image akashgupta1116 Updated on ・11 min read

If you wish to try out the final outcome check out the live demo.

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 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 library which lets us connect to the platform over 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:

What tools will we use?

1. Ably

Alt Text

Ably is an excellent realtime messaging platform that makes it easy to add realtime functionality to our applications. It comes with both Realtime and REST libraries to be used in accordance with the use case.
For our app, we’ll use Ably’s realtime library which lets us connect to the platform over WebSockets.

2. Chart.js

Alt Text

Chart.js 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

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

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

Alt Text

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

Alt Text

Let's Get Started.

Set up an Ably account.

In order to run these tutorials locally, you will need an Ably API key. If you are not already signed up, you should sign up now for a free Ably account.

Alt Text

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

Alt Text

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 using the Token Authentication scheme.

Alt Text

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

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 to do this. Let’s create the app from the terminal by running:

npx create-react-app voting-app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

‘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 .
Voting Interface - 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:

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;
Enter fullscreen mode Exit fullscreen mode

Here we are using react-router.

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.

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

export default Voting;
Enter fullscreen mode Exit fullscreen mode

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.

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;
Enter fullscreen mode Exit fullscreen mode

Here, we have utilized the Chart.js 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.

Now add the Dashboard Component to the Voting Component;

import Dashboard from './Dashboard';
...
class Voting extends Component {
...
 render(){
...
  <Dashboard/>
...
 }
}
export default Voting; 
Enter fullscreen mode Exit fullscreen mode

Token Authentication with Ably

Now, let’s instantiate the Ably realtime library as well as the channels that we’ll use to share realtime data.

The two schemes supported by Ably are Basic Authentication, which uses your private API key, and 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.

As you’ll be using the recommended token authentication scheme 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. Ably TokenRequests cannot be tampered with due to being signed, must be used soon after creation, and can only be used once.

Alt Text

Express.js 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:

{
 "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"
}
Enter fullscreen mode Exit fullscreen mode

Then you need to set up a vanilla HTTP Express.js server in server.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);
});
Enter fullscreen mode Exit fullscreen mode

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

.env

PORT = 3001
ABLY_API_KEY = <YOUR_ABLY_API_KEY>
Enter fullscreen mode Exit fullscreen mode

Our folder structure will look like this.

Alt Text

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 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:

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);
   }
 });
};
Enter fullscreen mode Exit fullscreen mode

Here, we have created two URLs ‘publish’ and ‘subscribe’ to provide the desired capability. If an API key must be shared with a third party, then it is recommended that the 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.

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);
     });

 }

Enter fullscreen mode Exit fullscreen mode

For cleaning of connections add the following code.

componentWillUnmount(){
  realTime.connection.off()
}
Enter fullscreen mode Exit fullscreen mode

In /voting-app/src/components/Dashboard.js add the following code:

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,
         },
       });
     });
   });
 }
Enter fullscreen mode Exit fullscreen mode

For cleaning of connection and subscription in Dashboard component add following code:

componentWillUnmount(){
 myVotingChannel.unsubscribe()
  realTime.connection.off()
}
Enter fullscreen mode Exit fullscreen mode

Bringing it all together

Run:

npm run dev
Enter fullscreen mode Exit fullscreen mode

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.

You can compare your app with live demo.

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 If you would like to find out more about how channels, Pub/Sub works, see the Realtime channels & messages documentation or better still learn more about the complete set of Ably features.

Discussion

pic
Editor guide