This blog post was written under the Pusher Guest Writer program.

These days Social has become the buzzword and we all want our apps to be the centre of these amazing social conversations. Comments on a post, video, update or any feature of your new app is a great way to add fun & enriching social conversations to your App.

If these conversations can be Realtime, then it’s even better, so in this blog post we will be discussing how we can create a realtime comment feature for our web apps using Pusher with Vanilla Javascript on front end & NodeJS on the backend.

We will call this realtime comment system Flash Comments, which can be re-used for multiple posts/features in your app and can generate amazing conversations in real time. Only basic HTML, CSS & JS knowledge is required to follow through this blog post. Our app will look something like this:

Sections

  • Brief Introduction to Pusher
  • Signing Up with Pusher
  • NodeJS & Express App for exposing a Comment Creation API & trigger Pusher Event
  • Front End using Vanilla JS subscribing to Channel

** Skip the first two sections, if you have already signed up with Pusher.

Brief Introduction to Pusher

Pusher is an amazing platform which abstracts the complexities of implementing a Realtime system on our own using Websockets or Long Polling. We can instantly add realtime features to our existing web applications using Pusher as it supports wide variety of SDKs. Integration kits are available for variety of front end libraries like Backbone, React, Angular, jQuery etc and also backend platforms/languages like .NET, Java, Python, Ruby, PHP, GO etc.

Signing up with Pusher

You can create a free account in Pusher at this link http://pusher.com/signup. After you signup and login for the first time, you will be asked to create a new app as seen in the picture below. You will have to fill in some information about your project and also the front end library or backend language you will be building your app with. You also have an option to select the cluster of Pusher based on your users location distribution, I have chosen ap2 (Mumbai, India) as I may be building an app for the India region.

For this particular blog post, we will be selecting Vanilla JS for the front end and NodeJS for the backend as seen in the picture above. This will just show you a set of starter sample codes for these selections, but you can use any integration kit later on with this app.

NodeJS App

Initialising Node Project

You can create a new folder named flash-comments and run the following command at the root of the folder:

npm init

It will ask you bunch of information regarding the app and it will create a new package.json file inside your folder.

We will be using the fairly simple and popular Express framework in Node. Now, we will install the important packages that will be used in our minimal Express app.

npm install -g express body-parser path --save

After installing all required npm modules, now we will create an entry point file for our Node app as server.js inside the root folder. Add the following basic code for a basic HTTP Server to be run using port 9000.

var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');

var app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));

// Error Handler for 404 Pages
app.use(function(req, res, next) {
    var error404 = new Error('Route Not Found');
    error404.status = 404;
    next(error404);
});

module.exports = app;

app.listen(9000, function(){
  console.log('Example app listening on port 9000!')
});

Pusher has an open source NPM module for NodeJS integrations which we will be using. It provides a set of utility methods to integrate with Pusher APIs using a unique appId, key & a secret. We will first install the pusher npm module using the following command:

npm install pusher --save

Now, we can use require to get the Pusher module and to create a new instance passing an options object with important keys to initialise our integration. For this blog post, I have put random keys; you will have to obtain it for your app from the Pusher dashboard.

var Pusher = require('pusher');

var pusher = new Pusher({
  appId: '303964',
  key: '82XXXXXXXXXXXXXXXXXb5',
  secret: '7bXXXXXXXXXXXXXXXX9e',
  cluster: 'ap2',
  encrypted: true
});

var app = express();
...

You will have to replace the appId, key & a secret with values specific to your own app. After this, we will write code for a new API which will be used to create a new comment. This api will expose the route /comment with HTTP POST method and will expect an object for comment with the properties name, email & comment. Add the following code to your server.js file before the app.listen part.

app.post('/comment', function(req, res){
  console.log(req.body);
  var newComment = {
    name: req.body.name,
    email: req.body.email,
    comment: req.body.comment
  }
  pusher.trigger('flash-comments', 'new_comment', newComment);
  res.json({ created: true });
});

In the above code, we have extracted the data from req.body into a newComment object and then used it to call the trigger method on Pusher instance.

Important Pusher Concepts

Channel

In Pusher, we have a conceptual grouping called Channels and it provides the basic way to filter data in Pusher. A Channel can represent many entities in a real world application. For example: In our comments app, a channel can be comments for a specific Article, video, blog post, photo, live streaming of an event etc.

We would create a new unique channel id for each of these entities to uniquely identify or group data like comments associated with any one of these. Two unique live streaming videos should also have separate Channel so that we can show the respective live comments stream on their respective pages.

So we will create a new unique channel for each entity with their unique id, so for example a Youtube video comments channel can be named comments-youtube-234.

There are three types of channel

  • Public Channel – can be subscribed by anyone who knows the name of the channel.
  • Private Channel – channel which can be subscribed by authorised users only. If the channel name has a private- prefix, it will be regarded as a private channel.
  • Presence Channel – this is a special channel type similar to private as only authorised users can subscribe, where the subscribers list is also maintained and notified to other users also. Channel name should have a prefix presence-

We will use a public channel in our blog post which we are naming as flash-comments but you should ideally use a private channel for commenting systems with unique name for each entity you want to enable commenting feature.

Event

Now, the real data in pusher is transmitted through events which is the primary way of packaging messages. An event can be triggered by a backend or even client in special cases for any particular channel. A channel is required to ensure that your message reaches the intended recipient.

We give a unique name to each event so that we can setup handlers for receiving and processing these event messages at each of our client end who has subscribed to any channel.

Pusher Trigger Method

Now we will understand our server side code for sending an Event to the pusher channel flash-comments.

...
pusher.trigger('flash-comments', 'new_comment', newComment);
...

We are using the .trigger(channel-name,event-name, payload)** to send an **Event from the server whenever the POST API is called for creating a new comment. For the simplicity of this blog post, we will not use any database to save and persist the comments but in a production system, you would be required to store a comment corresponding to a unique entity id like a Youtube Video ID or a Blog Post ID.

Now, we can run our server using node server command. Our web service will be accessible on the URL http://localhost:9000/comment.We can write a POST request using any chrome extension like POSTMan or even CURL to test if it returns { "created":"true" } .

The Curl command to test your POST api will be as follows:

curl -H "Content-Type: appliaction/json" -X POST -d '{"name":"Rahat Khanna","email":"rahat.khanna@yahoo.co.in","comment":"Creating a sample comment"}' http://localhost:9000/comment

The output of running the CURL command on the terminal will look like this:

Front End using Vanilla JS

Now, we will be writing the most crucial part, the front end code using Vanilla JS. In the front end code we will be developing a Comments box section which would have following 2 features

  • Display all the Live Comments added to the channel with a smooth animation
  • Add new comment to the live comments by hitting the POST Api we have just created

Step 1: Create a folder named public & create an index.html

We have already written code in our server.js to serve static content from public folder, so we will write all our front end code in this folder.

Please create a new folder public and also create an empty index.html for now.

Step 2: Add Boilerplate Code to our index.html

We will be adding some basic boilerplate code to setup the base structure for our web app like Header, Sections where content like video or blog post can be put and also the section which will contain our Flash Comments box.

<!DOCTYPE>
<html>
    <head>
        <title>Making Social Comments Realtime & Fun with Pusher using Javascript like the Flash</title>
        <link rel="stylesheet" href="https://unpkg.com/purecss@0.6.2/build/pure-min.css" integrity="sha384-UQiGfs9ICog+LwheBSRCt1o5cbyKIHbwjWscjemyBMT9YCUMZffs6UqUTd0hObXD" crossorigin="anonymous">
        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway:200">
        <link rel="stylesheet" href="./style.css">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <header>
            <div class="logo">
                <img src="./assets/pusher-logo.png" />
            </div>
        </header>
        <section>
            <img class="flash-logo" src="./assets/flash-logo.jpg" />
            <h2>Flash Comments - Super Fast</h2>
            <div class="post">
      <!-- Put here Content like Youtube Video, Blog Post or Live Stream -->
            </div>
        </section>
        <section>

           <div class="flash-comments">
                <div class="header">
                    <div><img src="./assets/comments.png"></div>
                    <div class="text">Comments</div>
                </div>
                <form class="pure-form" id="comment-form">
                    <!-- Here we will put a form to create new comment -->
                </form>
                 <div class="comments-list" id="comments-list">
                    <!-- Here we will display live comments -->
                </div>
            </div>
        </section>
    </body>
</html>

Step 3: Create style.css file

Now we will also create a style.css file to contain the important css code for styling our web app and the flash comments component. We will add basic styles to render our skeleton.

body{
    margin:0;
    padding:0;
    overflow: hidden;
    font-family: Raleway;
}

header{
    background: #2b303b;
    height: 50px;
    width:100%;
    display: flex;
    color:#fff;
}

.flash-logo{
    height:60px;
    border-radius: 8px;
    float: left;
    margin-right: 15px;
}


section{
    padding: 15px;
    width:calc(100% - 45px);
}

.logo img{
    height: 35px;
    padding: 6px;
    margin-left: 20px;
}


.flash-comments{
    border:1px solid #aeaeae;
    border-radius: 10px;
    width:50%;
    overflow: hidden;
}

.post{
    padding-top:10px;
}

.flash-comments .header{
    display: flex;
    padding: 5px 20px;
    border-bottom: 1px solid #eaeaea;
}

.flash-comments .header .text{
    padding-left:15px;
    line-height: 25px;
}

.flash-comments .comment{
    display: flex;
    border-bottom:1px solid #eaeaea;
    padding: 4px;
}

Step 4: Add the Pusher JS Library & create app.js

Now we will add the Pusher Vanilla JS Library available on its CDN to use it to integrate with the Pusher system using plain Javascript code. Please add the following script tag at the end of the body before its closing tag:

...
<script type="text/javascript" src="https://js.pusher.com/3.2/pusher.min.js"></script>
</body>
...

Also, create a new app.js file where we will be writing all our code and also import the same in our index.html file after the script tag to import Pusher JS file.

<script type="text/javascript" src="https://js.pusher.com/3.2/pusher.min.js"></script>
<script type="text/javascript" src="./app.js"></script>
</body>

In our file app.js now, we will write code to initialise the Pusher instance using the unique client API key we have got from the Pusher dashboard. We will also pass an object specifying the cluster and setting the flag encrypted to true so that all messaging & communication is encrypted. We will also use the pusher.subscribe('channel-name') to listen to all events for a specific channel.

We will create a Javascript IIFE (Immediately Invoking Functions) to create a private scope so that we do not pollute global scope. Please add the following code to app.js file:

// Using IIFE for Implementing Module Pattern to keep the Local Space for the JS Variables
(function() {
    // Enable pusher logging - don't include this in production
    Pusher.logToConsole = true;

    var serverUrl = "/",
        comments = [],
        pusher = new Pusher('82XXXXXXXXXXXXXX5', {
          cluster: 'ap2',
          encrypted: true
        }),
        // Subscribing to the 'flash-comments' Channel
        channel = pusher.subscribe('flash-comments');

})();

Step 5: Creating Form for adding new comment

Now, we will create the form controls for letting the user input their name, email and comment text for creating a new comment using our Node API and Pusher. We will add the following HTML code inside the existing form tag to create form.

<form class="pure-form" id="comment-form">
  <div class="comment-form">
      <div class="left-side">
           <div class="row">
               <input type="text" required placeholder="enter your name" id="new_comment_name">
               <input placeholder="enter valid email" required type="email" id="new_comment_email">
            </div>
            <div class="row">
                <textarea placeholder="enter comment text" required id="new_comment_text" rows="3"></textarea>
            </div>
      </div>
     <div class="right-side">
            <button type="submit" class="button-secondary pure-button">Send Comment</button>
     </div>
 </div>
</form>

In the form code above, we have used HTML5 validations like required & type=email which would not allow user to keep these fields blank or submit an invalid email. These validations will automatically work in most browsers which support HTML5 form validations.

Also, we will be adding the following css to style the form:

.flash-comments form{
    margin-bottom: 0px;
}

.flash-comments .comment-form{
    display: flex;
    padding: 6px;
    border-bottom:1px solid #eaeaea;
}

.comment-form .left-side{
    flex: 5;
    display: flex;
    flex-direction: column;
    padding-right: 5px;
}

.comment-form .left-side .row{
    flex: 0 auto;
    display: flex;
    align-content: center;
}

.comment-form .left-side .row input{
    height: 32px;
    width: 50%;
}

.comment-form .left-side .row textarea{
    height: 42px;
    margin-top:8px;
}

.comment-form .right-side{
    flex:1;
    display: flex;
    justify-content: center;
}

.comment-form .right-side button{
    white-space: pre-wrap;
}

.comment-form textarea{
    width:100%;
}

.button-secondary {
    background: rgb(66, 184, 221); /* this is a light blue */
    color: white;
    border-radius: 4px;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}

After building the visual form, now we need to attach an event handler to the Submit event of the form. We will do that using the following code in the app.js file probably at the top after the var declarations:

var commentForm = document.getElementById('comment-form');

// Adding to Comment Form Submit Event
commentForm.addEventListener("submit", addNewComment);

Now, we will write the code for implementation of the handler addNewComment with the following code:

function addNewComment(event){
      event.preventDefault();
      var newComment = {
        "name": document.getElementById('new_comment_name').value,
        "email": document.getElementById('new_comment_email').value,
        "comment": document.getElementById('new_comment_text').value
      }

      var xhr = new XMLHttpRequest();
      xhr.open("POST", serverUrl+"comment", true);
      xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
      xhr.onreadystatechange = function () {
        if (xhr.readyState != 4 || xhr.status != 200) return;

        // On Success of creating a new Comment
        console.log("Success: " + xhr.responseText);
        commentForm.reset();
      };
      xhr.send(JSON.stringify(newComment));
}

We are using native XHR request to make an AJAX request to the Node API. You can use either jQuery Ajax or any framework-specific Ajax method in your app. Now if we run our application, then fill the form and submit it, then we will see a Success: { created: true }message in our browser developer tools console.

Also, we can see the Pusher Dashboard to see the stats about Event Messages sent for any channel.

Step 6: Display List of Comments Received for this Channel

Now, we will bind to the new_comment event on this channel flash-comments so that we can receive any message about new comment creation done from any client in realtime, and we can display all those comments.

We will first add a template for a new comment in our index.html file inside the div tag with id="comments-list".

<div class="comments-list" id="comments-list">
    <script id="comment-template" type="text/x-template">
        <div class="user-icon">
            <img src="./assets/user.png" />
        </div>
        <div class="comment-info">
            <div class="row">
                  <div class="name">{{name}}</div>
                  <div class="email">{{email}}</div>
             </div>
             <div class="row">
                   <div class="text">{{comment}}</div>
             </div>
         </div>
     </script>
</div>

Now, we will write the Javascript code to bind to the new_comment event on the pusher channel instance we have subscribed. Whenever the new_comment event will be fired, we will take the template innerHTML content and replace the placeholders {{name}}, {{email}} & {{comment}}with the data passed along with the event and append them to the comments-list div element.

var commentsList = document.getElementById('comments-list'),
    commentTemplate = document.getElementById('comment-template');

// Binding to Pusher Event on our 'flash-comments' Channel
channel.bind('new_comment',newCommentReceived);

// New Comment Received Event Handler
    // We will take the Comment Template, replace placeholders & append to commentsList
    function newCommentReceived(data){
      var newCommentHtml = commentTemplate.innerHTML.replace('{{name}}',data.name);
      newCommentHtml = newCommentHtml.replace('{{email}}',data.email);
      newCommentHtml = newCommentHtml.replace('{{comment}}',data.comment);
      var newCommentNode = document.createElement('div');
      newCommentNode.classList.add('comment');
      newCommentNode.innerHTML = newCommentHtml;
      commentsList.appendChild(newCommentNode);
    }

Using the above code, a new div tag representing the new comment will automatically be created and appended to the comments-list container. We will now add the following css to nicely display the list of comments and also animate whenever a new comment appears on the list.

.flash-comments .user-icon{
    flex: 0 80px;
    display: flex;
    justify-content: center;
}

.flash-comments .user-icon img{
    height:45px;
}

.flash-comments .comment-info{
    flex:5;
}

.flash-comments .comment-info .row{
    display: flex;
}

.flash-comments .comment-info .name{
    color: #000;
}

.flash-comments .comment-info .email{
    color: #aeaeae;
    margin-left: 10px;
}

.flash-comments .comment-info .text{
    padding-top:6px;
    font-size: 13px;
}

/* CSS Code for Animating Comment Element */
.flash-comments .comment{
  animation: animationFrames ease 1s;
  animation-iteration-count: 1;
  transform-origin: 50% 50%;
  animation-fill-mode:forwards; /*when the spec is finished*/
  -webkit-animation: animationFrames ease 1s;
  -webkit-animation-iteration-count: 1;
  -webkit-transform-origin: 50% 50%;
  -webkit-animation-fill-mode:forwards; /*Chrome 16+, Safari 4+*/ 
  -moz-animation: animationFrames ease 1s;
  -moz-animation-iteration-count: 1;
  -moz-transform-origin: 50% 50%;
  -moz-animation-fill-mode:forwards; /*FF 5+*/
  -o-animation: animationFrames ease 1s;
  -o-animation-iteration-count: 1;
  -o-transform-origin: 50% 50%;
  -o-animation-fill-mode:forwards; /*Not implemented yet*/
  -ms-animation: animationFrames ease 1s;
  -ms-animation-iteration-count: 1;
  -ms-transform-origin: 50% 50%;
  -ms-animation-fill-mode:forwards; /*IE 10+*/
}

@keyframes animationFrames{
  0% {
    opacity:0;
    transform:  translate(-1500px,0px)  ;
  }
  60% {
    opacity:1;
    transform:  translate(30px,0px)  ;
  }
  80% {
    transform:  translate(-10px,0px)  ;
  }
  100% {
    opacity:1;
    transform:  translate(0px,0px)  ;
  }
}

@-moz-keyframes animationFrames{
  0% {
    opacity:0;
    -moz-transform:  translate(-1500px,0px)  ;
  }
  60% {
    opacity:1;
    -moz-transform:  translate(30px,0px)  ;
  }
  80% {
    -moz-transform:  translate(-10px,0px)  ;
  }
  100% {
    opacity:1;
    -moz-transform:  translate(0px,0px)  ;
  }
}

@-webkit-keyframes animationFrames {
  0% {
    opacity:0;
    -webkit-transform:  translate(-1500px,0px)  ;
  }
  60% {
    opacity:1;
    -webkit-transform:  translate(30px,0px)  ;
  }
  80% {
    -webkit-transform:  translate(-10px,0px)  ;
  }
  100% {
    opacity:1;
    -webkit-transform:  translate(0px,0px)  ;
  }
}

@-o-keyframes animationFrames {
  0% {
    opacity:0;
    -o-transform:  translate(-1500px,0px)  ;
  }
  60% {
    opacity:1;
    -o-transform:  translate(30px,0px)  ;
  }
  80% {
    -o-transform:  translate(-10px,0px)  ;
  }
  100% {
    opacity:1;
    -o-transform:  translate(0px,0px)  ;
  }
}

@-ms-keyframes animationFrames {
  0% {
    opacity:0;
    -ms-transform:  translate(-1500px,0px)  ;
  }
  60% {
    opacity:1;
    -ms-transform:  translate(30px,0px)  ;
  }
  80% {
    -ms-transform:  translate(-10px,0px)  ;
  }
  100% {
    opacity:1;
    -ms-transform:  translate(0px,0px)  ;
  }
}

Now, you can run the app we have built, either in 2 different browsers or one in normal browser and the other in incognito window, and add multiple comments. We can see that the live comments will be added in realtime with a smooth animation.

The complete code for this tutorial is available on this Github link https://github.com/mappmechanic/flash-comments.

Conclusion

We have built a nice web app with live comment feature using Pusher, NodeJS and Vanilla Javascript. We can use this component with any of our applications and enable live comments for variety of social entities like Videos, Blog Post, Polls, Articles and live streams.

We have used the NodeJS server to create a REST API to get a new comment and then trigger a Pusher event on a specific channel. For any real world application, we can take a unique id for each entity and use a unique channel name for any entity. In a production scenario we can also store the comments in a persistent storage and then later retrieve them.

We have also created a Front End app, which will connect to the Pusher API using pusher js library. We have created a form to hit the Node API which will trigger new_comment event. Comments are displayed in realtime with an animation using the bind method on the channel instance.