Realtime everywhere! If you are an ardent follower of the trends in the industry, especially the web development ecosystem, you will agree with me that a larger percentage of users appreciate realtime responses from web applications.
This may be in the form of notifications, events, alerts, instant messaging or anything similar to these. Only a few platforms offer realtime technologies applicable for use in realtime digital experiences like Gaming and Gambling, Chat and Social, Data Content, Notifications and Alert and so on. This is where Ably as a company shines.
To explore the realtime technology, I have always wanted to try out Ably and after reading this post I had to get down to work. So when I finally got the chance, I was able to explore the awesomeness of realtime functionality as offered by Ably by building the following application:
This is a realtime opinion poll built with Nest.js and powered by Ably. In this article, I am going to document the stage by stage process of how I was able to build the demo shown above.
To get the most out of this tutorial, a basic understanding of TypeScript and Node.js is advised.
we will use the following tools to build this app :
Nest.js
Ably Realtime
Its super easy to setup a new application using Nest.js, but before we proceed, it is assumed that you already have node and npm installed. If not, kindly check the node.js and npm websites for installation steps.
To begin, use the commands below to clone a new starter repository, change directory into the newly created project folder and finally install all the required dependencies for Nest.js application.
$ git clone https://github.com/nestjs/typescript-starter.git ably-nest-poll
$ cd ably-nest-poll
$ npm install
$ npm run start
This will start the application on the default port used by Nest.js (3000). Head over to http://localhost:3000
If you don’t already have an ably account, head over to their website and create one.
Follow the remaining process and 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 Basic Authentication scheme.
You will see that by default, Ably creates an app for you which you can readily start using. However, you can also create a new application and configure it according to your needs.
I have named mine 'ably-nest-poll'
. Feel free to choose any name that suits your purpose.
Use the Node Package Manager to install dependencies for the application:
npm install ejs ably --save
One of the core files in Nest.js is 'main.ts’
This file contains the necessary functions with the responsibility of bootstrapping our application. Nest favours the popular MVC pattern and therefore allows the usage of template engine. Open '.src/main.ts’
and fill with :
import { NestFactory } from '@nestjs/core';import { ApplicationModule } from './app.module';
//import express moduleimport * as express from 'express';
// pathimport * as path from 'path';
async function bootstrap() {const app = await NestFactory.create(ApplicationModule);
// A public folder to serve static filesapp.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname + '/views');
// set ejs as the view engineapp.set('view engine', 'ejs');
await app.listen(3000);}bootstrap();
The only addition I have made to the default configuration of this file is to import Express module, path and finally set ejs as the view engine for the application.
In order to render the HTML output and display the application to users, we will create a folder called views
within the src
folder. Now, within this newly created folder, create a new file and name it index.ejs
Then add the following code to your 'index.ejs'
file:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.min.css"><title>Realtime Poll</title></head><body>
<div class="container">
<h1> Marvel Movies </h1>
<p> Select your favorite Marvel Movie </p>
<form id="opinion-form">
<p>
<input type="radio" name="movie" id="avengers" value="The Avengers">
<label for="avengers">The Avengers</label>
</p>
<p>
<input type="radio" name="movie" id="black-panther" value="Black Panther">
<label for="black-panther">Black Panther</label>
</p>
<p>
<input type="radio" name="movie" id="captain-america" value="Captain America">
<label for="captain-america">Captain America</label>
</p>
<p>
<input type="radio" name="movie" id="other" value="Other">
<label for="other">Something Else </label>
</p>
<input type="submit" value="Vote" class="btn btn-success"/>
</form>
<br><br>
<div id="chart-container" style="height:300px;width:100%;">
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.1/axios.js"></script>
<script src="http://cdn.ably.io/lib/ably.min-1.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.min.js"></script>
<script src="/main.js"></script>
</body></html>
This will serve as the homepage for our realtime poll application. In order to make this page look presentable, I included a CDN file each for Materialize, Ably, CanvasJS and JQuery. Further, I’ve included a form with radio-button input fields and finally linked a custom script named main.js
that we’ll visit later on in this tutorial.
Route is handled within Nest.js by the controller layer. This receives the incoming requests and returns a response to the client. Nest uses a Controller metadata '@Controller'
to map routes to a specific controller. For now, we will make use of the default controller to set up the homepage for our demo app. So edit '.src/app.controller.ts'
and add the code shown below :
import { Get, Controller, Res } from '@nestjs/common';
@Controller()export class AppController {@Get()root(@Res() res) {res.render('index');}}
The above code lets us manipulate the response by injecting the response object using the @Res()
decorator. This will ensure that Nest maps every '/'
route to the 'index.ejs'
file.
The next thing we need to build is the controller for poll. This will handle every request once a user selects a choice and submit votes. So go ahead and create a new folder named poll in your 'src'
folder and then create a file 'poll.controller.ts'
within it. Paste the following code in the newly created file.
import { Controller, Post, Res, Body } from '@nestjs/common';
// import pollServiceimport { PollService } from './poll.service';
@Controller('poll')export class PollController {
// inject service
**constructor**(**private** pollService: PollService) {}
@Post()
submitVote(@Res() res, @Body() poll: **string**) {
**this**.pollService.create(poll);
res.render('index');
}
}
A quick peek into the code above, you will realize that we imported a service and injected it into the controller through the constructor, this is recommended by Nest in order to ensure controllers handles only HTTP requests. This service will perform a task of publishing payload to Ably. We will create this service PollService
in a bit.
In addition, the @Controller(‘poll’) tells the framework that we expect this controller to respond to requests posted to /poll route.
Basically, we want to utilize one of the core functionalities of Ably, which is publishing messages or payload to Ably and ensure that every connected client or device on that channel receives them in realtime by means of subscription. This is where Ably really shines; you get to focus on building apps and allow the platform to use their internal infrastructure to manage communication without you having to worry about it
Lets create a component as a service within Nest.js . This will be used to publish a payload to Ably on a specified channel.
Controllers in Nest.js only handle HTTP requests and delegate complex tasks to components. Components here are plain TypeScript classes with @Component decorator. So create a new file within poll folder named poll.service.ts
import { Component } from '@nestjs/common';
@Component()export class PollService {
**private** poll: **string**;
create(poll) {
**const** Ably = require('ably');
// replace with your API Key
**var** ably = **new** Ably.Realtime('YOUR\_KEY');
**var** channel = ably.channels.get('ably-nest');
**const** data = {
points: 1,
movie: poll.movie
};
channel.publish('vote', data);
}
}
Here, I required the ably module that was installed earlier and passed in the required API key. Also, I created a unique channel ably-nest
for clients to subscribe to. I also have the publish method which takes in two parameters, one is an optional message event name and the other is a payload to be published.
At the moment, our application doesn’t recognise any newly created controller and service. We need to change this by editing our module file 'app.module.ts'
and put controller into the 'controller'
array and service into 'components'
array of the '@Module()
decorator respectively.
import { PollController } from './poll/poll.controller';import { Module } from '@nestjs/common';import { AppController } from './app.controller';import { PollService } from './poll/poll.service';
@Module({imports: [],controllers: [AppController, PollController],components: [PollService],})export class ApplicationModule {}
Just a quick recap before the final stage. So far, in this tutorial, we have
ably-nest
on Ably andRemember that we included a custom 'main.js'
file in our index.ejs
file? Go ahead a create a new folder called public
within the src
folder then create the main.js
file within it. Further, add the following code to the file.
const form = document.getElementById('opinion-form');
// form submit eventform.addEventListener('submit', (e) => {const choice = document.querySelector('input[name=movie]:checked').value;const data = {movie: choice};
axios.post('/poll', data).then( (data) => {
console.log(data);
});
e.preventDefault();
});
let dataPoints = [{label: 'The Avengers', y: 0},{label: 'Black Panther', y: 0},{label: 'Captain America', y: 0},{label: 'Other', y: 0},];
const chartContainer = document.querySelector('#chart-container');
if (chartContainer) {const chart = new CanvasJS.Chart('chart-container', {animationEnabled: true,theme: 'theme1',title: {text: 'Favorite Movies'},data: [{type: 'column',dataPoints: dataPoints}]});
chart.render();
**var** ably = **new** Ably.Realtime('YOUR\_KEY');
**var** channel = ably.channels.get('ably-nest');
channel.subscribe('vote', **function**(poll) {
dataPoints = dataPoints.map(x => {
**if** (x.label == poll.data.movie) {
x.y += poll.data.points;
**return** x;
} **else** {
**return** x;
}
});
chart.render();
});
}
This content of this file is self explanatory, we handle form submission and post to the poll
route using axios.
We also set a default dataPoints
for our chart and finally subscribe to the payload posted from the server.
Don’t forget to replace the YOUR_KEY
with the appropriate API KEY from your dashboard.
Restart the development server again if it is currently running and navigate to http://localhost:3000
or http://127.0.0.1:3000
to check it out.
And that is it.
If you miss any of the steps, you can find the code for this demo here on GitHub
We have successfully achieved two things in this tutorial:
If you would like to find out more about how channels, publishing and subscribing works, see the Realtime channels & messages documentation or better still learn more about the complete set of Ably features.