Build a realtime app with Adonis

Introduction

Adonis is a full stack, open source MVC framework for Node.js. The framework is inspired by the PHP framework Laravel. As at the time of this tutorial, it has over 2.8k stars on GitHub.

Adonis focuses on the developers productivity and efficiency over anything else. It also comes with a lot of features including the following:

  • Lucid ORM
  • Database Migrations
  • Authentication System
  • OAuth
  • Mailing System
  • Data Validator

Adonis has very detailed documentation and a supportive community of users who are engaging on Twitter.

In this tutorial we are going to see some of the interesting features of Adonis by building an app that pushes realtime messages to all connected clients using Pusher.

Demo

Here is what the final result of the app would look like:

build-realtime-app-adonis-pusher-demo

Prerequisites

To get started, you need knowledge of Node.js and JavaScript. The following must be installed on your machine:

  • Node.js
  • NPM(Bundled with Node.js installer)

Node.js is an open-source, cross-platform JavaScript run-time environment for executing JavaScript on a server.

Set up an Adonis project

Open your terminal and type this command to install Adonis CLI and create a new adonis app:

1# if you don't have Adonis CLI installed on your machine. 
2    $ npm install -g @adonisjs/cli
3    
4    # Create a new adonis app and move into the app directory
5    $ adonis new adonis-event-pusher && cd adonis-event-pusher

Start the server and test if it's working:

1$ adonis serve --dev
2    2017-10-18T09:09:16.649Z - info: serving app on http://127.0.0.1:3333

Open your browser and make a request to http://127.0.0.1:3333. You should see the following:

build-realtime-app-adonis-pusher-first-run

Install the Pusher SDK

For Adonis to work with Pusher, we need to install pusher with npm or yarn into our project. To do so, run:

1#if you want to use npm
2    $ npm install pusher -save
3    
4    #if you want to use yarn
5    $ yarn add pusher

Update the welcome view

Go to the resources/views directory and replace the content of welcome.edge file with:

1// welcome.edge
2
3    <!DOCTYPE html>
4    <html lang="en">
5      <head>
6        <meta charset="UTF-8" />
7        <title>Adonis & Pusher</title>
8        <!-- Styles -->
9        {{ css('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css') }}
10        {{ css('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/css/bootstrap.min.css') }}
11        {{ css('style') }}
12      </head>
13      <body id="app-layout">
14        <nav class="navbar navbar-expand-md navbar-dark fixed-top">
15          <a class="navbar-brand" href="{{ route('welcomePage') }}"><i class="fa fa-cube"></i> Adonis & Pusher</a>
16          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
17          <span class="navbar-toggler-icon"></span>
18          </button>
19        </nav>
20        <div class="container" style="margin-top: 160px">
21          <div class="row">
22            <div class="col-md-2"></div>
23            <div class="col-md-8">
24              <div class="card">
25                <div class="card-header">Broadcast a Message</div>
26                <div class="card-body">
27                  <div class="container">
28                    <div class="row justify-content-md-center">
29                      <div class="col col-md-10">
30                        @if(old('status'))
31                        <div class="alert alert-success" role="alert">
32                          <button type="button" class="close" data-dismiss="alert" aria-label="Close">
33                          <span aria-hidden="true">×</span>
34                          </button>
35                          {{ old('status') }}
36                        </div>
37                        @endif
38                        <form method="POST" action="{{ route('sendMessage') }}">
39                          {{ csrfField() }}
40                          <div class="form-group row">
41                            <label class="col-md-3 col-form-label" for="email">
42                            Message
43                            </label>
44                            <div class="col-md-9">
45                              <input type="text" name="message" placeholder="Send Messege"
46                                autocomplete="off"
47                                class="form-control" required>
48                            </div>
49                          </div>
50                          <div class="form-group row">
51                            <div class="col-md-3"></div>
52                            <div class="col-md-6">
53                              <button type="submit" class="btn btn-primary btn-block">
54                              <i class="fa fa-btn fa-paper-plane"></i> Send Message
55                              </button>
56                            </div>
57                          </div>
58                        </form>
59                      </div>
60                    </div>
61                  </div>
62                </div>
63              </div>
64            </div>
65          </div>
66        </div>
67        {{ script('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.slim.min.js') }}
68        {{ script('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta/js/bootstrap.min.js') }}
69      </body>
70    </html>

As you can see, we are importing the CSS files using the css method. We do a similar thing with JavaScript, we use script method to import .js. Flash messages are used to display incoming messages from the server. Flash messages are messages stored temporarily in sessions by the server to display as browser notifications.

Refresh your browser:

build-realtime-app-adonis-pusher-2

Basic routes

We are going to define 3 basic routes for our application. One for displaying the form which you will use to collect the user message, another to render a sample frontend view and the last for broadcasting a messages via Event.

Go to the start/routes.js file and replace the content with:

1// routes.js
2
3    'use strict'
4    
5    const Route = use('Route')
6    const Event = use('Event')
7    
8    
9    Route.on('/').render('welcome')
10    
11    Route.on('/home').render('home')
12    
13    Route.post('/send', async ({request, session, response}) => {
14        const message = request.input('message')
15        
16        Event.fire('send.message', message)
17        
18        session.flash({ status: 'Message sent' })
19        return response.redirect('back')
20    
21    }).as('sendMessage')

The block pulls in Event and Route service providers.

The first route renders the welcome.edge file in the resources/views directory (which is where views are stored in Adonis).

The second route renders the home.edge file (which we will create soon) and the last route accepts a message from browser requests, fires an event and redirects the user back with a flash message.

Pusher setup

Pusher is a hosted service that makes it super-easy to add realtime data and functionality to web and mobile applications. Pusher is an abstracted real-time layer between clients and servers.

Let's setup pusher for our application. Head over to Pusher and create an account. You can sign in if you already have a account.

Register a new Pusher app instance. This registration provides credentials which can be used to communicate with the created Pusher instance. Copy the App ID, Key, Secret, and Cluster from the App Keys section and put them in the .``env file:

1// .env
2
3    PUSHER_KEY=<APP_KEY>
4    PUSHER_SECRET=<APP_SECRET>
5    PUSHER_APP_ID=<APP_ID>
6    PUSHER_CLUSTER=<APP_CLUSTER>

Connecting Adonis and Pusher

Create a file named events.js file inside the start directory. In this file, create an event which will be fired every time we need to send a message via the pusher channel:

1// events.js
2
3    const Event = use('Event')
4    const Pusher = require('pusher')
5    const Env = use('Env')
6    
7    let pusher = new Pusher({
8            appId: Env.get('PUSHER_APP_ID', ''),
9            key: Env.get('PUSHER_KEY', ''),
10            secret: Env.get('PUSHER_SECRET', ''),
11            cluster: Env.get('PUSHER_CLUSTER'),
12            encrypted: true
13    });
14    
15    Event.when('send.message', async (message) => {
16            pusher.trigger('adonis-channel', 'send-message', {
17                    message
18            });
19    })

We need to pull in the Event and env service providers. We are also importing the pusher module. Next, we create a Pusher instance and configured with the credentials that were received after creating a Pusher account.

Next, we registered a listener for the send.message event, after which we initialize and configure Pusher. This event was registered in the routes we created above to handle the message request.

When we are done with the pusher configuration, we trigger a send-message event on the adonis-channel with the trigger method.

Subscribing to Pusher events

The client needs to start listening to these events being emitted by Pusher. To do so, setup an Adonis view that displays incoming messages. Go to the resources/views directory and create a file called home.edge which would look like the following:

1// home.edge
2
3    <!DOCTYPE html>
4    <html>
5    <head>
6            <title>Adonis and Pusher awesomeness</title>
7            {{ css('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css') }}
8    </head>
9    <body>
10    <div class="container">
11            <div class="content">
12                    <h1>Adonis and Pusher</h1>
13                    <ul id="messages" class="list-group">
14                    </ul>
15            </div>
16    </div>
17    {{ script('https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js') }}
18    <!-- Include the Pusher Client library -->
19    {{ script('https://js.pusher.com/4.1/pusher.min.js') }}
20    <script>
21            //Open a connection to Pusher
22            let pusher = new Pusher('APP_KEY', {
23              cluster: 'APP_CLUSTER',
24              encrypted: true
25            });
26    
27            //Subscribe to the channel we specified in our Adonis Application
28            let channel = pusher.subscribe('adonis-channel')
29    
30            //Listen for events on the channel
31            channel.bind('send-message', (data) => {
32              let listItem = $("<li class='list-group-item'>" + data.message + "</li>")
33              $('#messages').prepend(listItem)
34            })
35    </script>
36    </body>
37    </html>

First, we include jQuery and the Pusher JavaScript client library which will enable us subscribe to Pusher events from the server.

Next, we initialize the Pusher service by passing in our App Key (replace with your actual keys), and some other options (cluster, encrypted). The initialized instance is used to subscribe to the adonis-channel channel.

Finally, we listen to the send-message event and update the view using jQuery, based on the content received via the event listener.

Navigate to http://127.0.0.1:3333/home to see the new view:

build-realtime-app-adonis-pusher-3

Conclusion

In summary, Pusher makes it easy to add realtime capabilities to your Adonis app. Install the SDK, import it, create an instance and start triggering realtime events. Whatever language domain (JS, Android, iOS, etc) it still uses the same subscription pattern to listen to realtime events in order to update a user interface. This is why it fits right into the Adonis framework, which is picking up momentum in the Node ecosystem. You can find the source code for this tutorial on GitHub.