> adonis install @adonisjs/websocket
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
AdonisJs offers a robust WebSocket Provider to serve real-time apps.
The server works on pure WebSocket connections (supported by all major browsers) and scales naturally within a cluster of Node.js processes.
As the WebSocket Provider is not installed by default, pull it from npm
:
> adonis install @adonisjs/websocket
Installing the provider adds the following files to your project:
config/socket.js
contains WebSocket server configuration.
start/socket.js
boots the WebSocket server and registers channels.
start/wsKernel.js
registers middleware to execute on channel subscription.
Next, register the provider inside the start/app.js
file:
const providers = [
'@adonisjs/websocket/providers/WsProvider'
]
Finally, instruct Ignitor to boot the WebSocket server in the root server.js
file:
const { Ignitor } = require('@adonisjs/ignitor')
new Ignitor(require('@adonisjs/fold'))
.appRoot(__dirname)
.wsServer() // boot the WebSocket server
.fireHttpServer()
.catch(console.error)
When running a Node.js cluster, the master node needs to connect the pub/sub communication between worker nodes.
To do so, add the following code to the top of the root server.js
file:
const cluster = require('cluster')
if (cluster.isMaster) {
for (let i=0; i < 4; i ++) {
cluster.fork()
}
require('@adonisjs/websocket/clusterPubSub')()
return
}
// ...
Let’s build a single room chat server for user messaging.
To keep things simple we won’t store user messages, just deliver them.
Open the start/socket.js
file and paste the following code:
const Ws = use('Ws')
Ws.channel('chat', 'ChatController')
We can also bind a closure to the Ws.channel method, but having a dedicated controller is the recommended practice.
|
Next, create the ChatController
using the make:controller
command:
> adonis make:controller Chat --type=ws
✔ create app/Controllers/Ws/ChatController.js
'use strict'
class ChatController {
constructor ({ socket, request }) {
this.socket = socket
this.request = request
}
}
module.exports = ChatController
Let’s switch from server to client and subscribe to the chat
channel.
First, copy the CSS and HTML template from this gist to the following locations:
CSS → public/style.css
HTML template → resources/views/chat.edge
Make sure to define a route to serve the HTML template. |
Next, create a public/chat.js
file and paste the code below to connect the client to the server (to keep things simple we’re using jQuery):
let ws = null
$(function () {
// Only connect when username is available
if (window.username) {
startChat()
}
})
function startChat () {
ws = adonis.Ws().connect()
ws.on('open', () => {
$('.connection-status').addClass('connected')
subscribeToChannel()
})
ws.on('error', () => {
$('.connection-status').removeClass('connected')
})
}
Then, add the channel subscription method, binding listeners to handle messages:
// ...
function subscribeToChannel () {
const chat = ws.subscribe('chat')
chat.on('error', () => {
$('.connection-status').removeClass('connected')
})
chat.on('message', (message) => {
$('.messages').append(`
<div class="message"><h3> ${message.username} </h3> <p> ${message.body} </p> </div>
`)
})
}
Finally, add the event handler to send a message when the Enter key is released:
// ...
$('#message').keyup(function (e) {
if (e.which === 13) {
e.preventDefault()
const message = $(this).val()
$(this).val('')
ws.getSubscription('chat').emit('message', {
username: window.username,
body: message
})
return
}
})
Now finished with the client, let’s switch back to the server.
Add the onMessage
event method to the ChatController
file:
class ChatController {
constructor ({ socket, request }) {
this.socket = socket
this.request = request
}
onMessage (message) {
this.socket.broadcastToAll('message', message)
}
}
In the example above, the onMessage
method sends the same message to all connected clients via the socket broadcastToAll
method.
Controllers keep your code organised by defining separate classes per channel.
WebSocket controllers are stored in the app/Controllers/Ws
directory.
A new controller instance is created per subscription with a context
object passed to its constructor, enabling the socket
instance to be unpacked like so:
class ChatController {
constructor ({ socket }) {
this.socket = socket
}
}
Bind to WebSocket events by creating controller methods with the same name:
class ChatController {
onMessage () {
// same as: socket.on('message')
}
onClose () {
// same as: socket.on('close')
}
onError () {
// same as: socket.on('error')
}
}
Event methods must be prefixed with the on keyword.
|