Building Realtime Web Applications Using Nest.js and Ably
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.
Pre-requisites
To get the most out of this tutorial, a basic understanding of TypeScript and Node.js is advised.
Tools
we will use the following tools to build this app :
- Nest.js : A progressive Node.js framework for building efficient and scalable server-side applications. It leverages on TypeScript to create reliable and well structured server-side application. If you are quite conversant with Angular, Nest.js gives you similar experience of building an Angular app, but on the backend. Despite using modern JavaScript (Typescript), it’s quite compatible with vanilla JavaScript which makes it very easy to get started with. You can read more about it here.
Nest.js
- Ably : An excellent realtime messaging platform that makes it easy to add realtime functionality to applications.
Ably Realtime
- Axios : A promise-based HTTP client that works both in the browser and in a node.js environment.
- CanvasJS: A responsive HTML5 Charting library for data visualisation.
- Lastly, we will also need to install few modules using npm
Setting up the Application
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
Run the Application
$ npm run start
This will start the application on the default port used by Nest.js (3000). Head over to http://localhost:3000
Ably Account Setup
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.
Dependencies
Use the Node Package Manager to install dependencies for the application:
npm install ejs ably --save
Bootstrap application
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 files
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname + '/views');
// set ejs as the view engine
app.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.
Set Up the View
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.
Handling Route
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.
Create a Controller
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.
Realtime Service
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.
Connecting the dots
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 {}
Plug in Ably client-side and update UI
Just a quick recap before the final stage. So far, in this tutorial, we have
- Created a form with radio buttons for users to cast and submit polls.
- We went further to create an account on Ably
- Set up an homepage
- Created a controller to handle post route.
- Setup a service to publish payloads to a named channel
ably-nest
on Ably and - Lastly, we registered the newly created controller and service within our application module.
Remember 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 event
form.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.
Bringing it all together
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
Conclusion
We have successfully achieved two things in this tutorial:
- Get introduced to building web applications using Nest.js
- Explore the realtime functionality offered by Ably
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.