DEV Community

Arthur Emanuel
Arthur Emanuel

Posted on • Updated on

Broadcasting in AdonisJS

In this tutorial we'll build a broadcasting module for AdonisJS which resembles Laravel Broadcasting features (you can even use Laravel Echo). This module will cover up many of the websockets use cases in a simple manner.

It is great for cases where the clients need to receive data in real-time, but don't need to send data in real-time.

Good use cases are:

  • Chats
  • Live dashboards
  • Sport scores

Bad use cases are:

  • Games
  • Work together platforms

Let's build it!

Scaffolding a new app

Create a new AdonisJS project

$ npm init create-adonis-ts-app broadcasting
$ yarn create adonis-ts-app broadcasting
Enter fullscreen mode Exit fullscreen mode

When prompted which project structure, select web and flag

Configure webpack for compiling frontend assets? true

Setting up our Broadcast server

Our broadcast module will be based in an open-source Pusher compatible server called pWS.

First, we will install it

$ npm i @soketi/pws
$ yarn add @soketi/pws
Enter fullscreen mode Exit fullscreen mode

We can start the server by running

$ npm pws-server start
$ yarn pws-server start
Enter fullscreen mode Exit fullscreen mode

But we need to configure it before running, so we will make a configuration file for it in config/broadcasting.ts

// config/broadcasting.ts
import Env from '@ioc:Adonis/Core/Env'

const broadcastingConfig = {
  port: Env.get('BROADCASTING_PORT', 6001),
  appId: Env.get('BROADCASTING_APP_ID', 'app-id'),
  appKey: Env.get('BROADCASTING_APP_KEY', 'app-key'),
  appSecret: Env.get('BROADCASTING_APP_KEY', 'app-secret'),
}

export default broadcastingConfig
Enter fullscreen mode Exit fullscreen mode

The configs won't get magically loaded into pWS, so we will make a command to start it. To start it we will use execa. So install it using:

$ npm i execa
$ yarn add execa
Enter fullscreen mode Exit fullscreen mode

and create a command with

$ node ace make:command StartPws
Enter fullscreen mode Exit fullscreen mode

The command will look like this:

// commands/StartPws.ts
import { BaseCommand } from '@adonisjs/core/build/standalone'
import execa from 'execa'

export default class StartPws extends BaseCommand {
  public static commandName = 'start:pws'
  public static description = 'Start the pWS server with Adonis Configs'
  public static settings = {
    loadApp: true,
    stayAlive: true,
  }

  public async run() {
    const broadcastingConfig = this.application.config.get('broadcasting')
    const command = `
      PORT=${broadcastingConfig.port}
      DEFAULT_APP_ID=${broadcastingConfig.appId}
      DEFAULT_APP_KEY=${broadcastingConfig.appKey}
      DEFAULT_APP_SECRET=${broadcastingConfig.appSecret}
      yarn pws-server start`
    await execa(command, { shell: true }).stdout?.pipe(process.stdout)
  }
}
Enter fullscreen mode Exit fullscreen mode

After creating the command, we need to regenerate the ace manifest, so it catches our new command, do it by running:

$ node ace generate:manifest
Enter fullscreen mode Exit fullscreen mode

Then you can run it with

$ node ace start:pws
Enter fullscreen mode Exit fullscreen mode

Broadcasting events

As pWS is a drop-in Pusher replacement, we can interact with it using any Pusher client, as AdonisJS is a node framework, we will use the node Pusher client. Start by installing the node Pusher client:

$ npm i pusher
$ yarn add pusher
Enter fullscreen mode Exit fullscreen mode

Then we will create a service to interact with the pWS server, it can be done as a simple service or as a AdonisJS provider, in this tutorial we will go the service way.

// app/Services/Broadcast.ts
import Pusher from 'pusher'
import broadcastingConfig from 'Config/broadcasting'
import Env from '@ioc:Adonis/Core/Env'

class Broadcast {
  private pusher = new Pusher({
    host: Env.get('HOST', 'localhost'),
    port: broadcastingConfig.port,
    appId: broadcastingConfig.appId,
    key: broadcastingConfig.appKey,
    secret: broadcastingConfig.appSecret,
  })

  public async broadcast(channel: string | string[], event: string, data: any) {
    const response = await this.pusher.trigger(channel, event, data)
    return response
  }
}

export default new Broadcast()
Enter fullscreen mode Exit fullscreen mode

With this service we can broadcast events by simply using

import Broadcast from 'App/Services/Broadcast'

await Broadcast.broadcast('test-channel', 'event', 'data')
Enter fullscreen mode Exit fullscreen mode

Listening to events

To listen to events in our frontend we can use PusherJS paired with Laravel Echo. Start by installing both:

$ npm i -D laravel-echo pusher-js
$ yarn add -D laravel-echo pusher-js
Enter fullscreen mode Exit fullscreen mode

Set them up in our frontend:

// resources/js/app.js
import '../css/app.css'
import Echo from 'laravel-echo'

window.Pusher = require('pusher-js')
window.Echo = new Echo({
  broadcaster: 'pusher',
  wsHost: 'localhost',
  wsPort: 6001,
  forceTLS: false,
  disableStats: true,
  key: 'app-key',
  namespace: '',
})
Enter fullscreen mode Exit fullscreen mode

Example setup

Append this to the end of resources/js/app.js

// resources/js/app.js
window.Echo.channel('messages').listen('message', (e) => {
  alert(JSON.stringify(e))
})
Enter fullscreen mode Exit fullscreen mode

Paste this into the welcome view (resources/views/welcome.edge)

<!-- resources/views/welcome.edge -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AdonisJS - A fully featured web framework for Node.js</title>
    @entryPointStyles('app')
    @entryPointScripts('app')
  </head>
  <body>
    <main>
      <div>
        <!-- Just to show off how it works. You can safely ignore that -->
        <form method="POST" action="/message">
          <input name="message" type="text" />
          <button>Send Message</button>
        </form>
      </div>
    </main>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

After setting up that, we just need to setup our message route to broadcast a message event:

// start/routes.ts
import Route from '@ioc:Adonis/Core/Route'
import Broadcast from 'App/Services/Broadcast'

Route.get('/', async ({ view }) => {
  return view.render('welcome')
})

Route.post('/message', async ({ request, response }) => {
  const message = request.input('message')
  await Broadcast.broadcast('messages', 'message', { message })
  return response.redirect().back()
})
Enter fullscreen mode Exit fullscreen mode

It's alive!

But it still doesn't works for private or presence channels, we will address that in next tutorial, stay tuned!

Discussion (0)