Index āŗ 18. Two-way WebSocket communication
Learn how to take the one-way WebSocket communication you saw in the Streamiverse example further to develop two-way WebSocket communication by building a simple echo server. It also introduces you to Kittenās first-class support for Alpine.js.
Topics covered
- Sending WebSocket messages from the client to the server.
- Echoing WebSocket messages back from the server to the client.
- Progressively enhancing your client using Alpine.js.
WebSocket echos
In the Streamiverse example, we used a WebSocket to stream in post updates but we could just as well have used Server-Sent Events (SSE) given that the communication was one-way.
š” Kitten does not support HTTP/2 so if you use SSE, people will be limited to 6 browser connections to your app. Given Kittenās excellent support for WebSockets and how efficient WebSockets are, and since HTTP/2 support is not currently planned, neither is first-class support for Server-Sent Events.
Whatās unique about WebSockets is that you can carry out asynchronous two-way communication.
So letās take advantage of that by creating a very simple WebSocket echo server that simply returns what we send it.
First, letās create the page:
index.page.js
export default () => kitten.html`
<page htmx htmx-websocket water>
<main
hx-ext='ws'
ws-connect='/echo.socket'
>
<h1>WebSocket Echo</h1>
<form id='message-form' ws-send>
<label for='message'>Message</label>
<input id='message' name='message' type='text' required>
<button type='submit'>Send</button>
</form>
<ul id='echos' hx-swap-oob='beforeend'></ul>
</main>
`
The only new thing here is that we have a form with the ws-send
And now, letās add the WebSocket route:
echo.socket.js
export default function ({ socket }) {
console.info('Echo socket: new client connection.', request.session)
socket.addEventListener('message', event => {
const message = JSON.parse(event.data).message
socket.send(`
<ul id='echos' hx-swap-oob='beforeend'>
<li><strong>ā${message}ā</strong> received on server.</li>
</ul>
`)
})
}
And thatās it. Now run the example and you should see your messages being echoed back to you.
Enhancing usability with Alpine.js
The most visible and annoying usability issue right now is that when you send a message, the message box is not cleared. So letās fix that using Kittenās built-in support for Alpine.js.
First, update the <page>
tag so Kitten knows to include Alpine.js on the page:
<page htmx htmx-websocket water alpinejs>
Then, update the <input>
element so it clears itself after the message has been sent:
<form id='message-form' ws-send>
ā¦
<input
id='message' name='message' type='text' required
x-data
@htmx:ws-after-send.window='$el.value = ""'
>
</form>
So whatās happening here is that weāre using Alpineās @
syntax to specify an inline handler for htmxās ws-after-send
event.
š” Remember that we can only use Alpine.js within Alpine.js components and the way you designate an Alpine.js component is to declare an
x-data
attribute on it. In this case, we have no actual data the component has to manage so we just use an emptyx-data
tag.
While htmx supports both camelCase
and kebap-case
for event names, Alpine.js can only work with kebap case since attribute names are case insensitive in HTML5. So while you will see events in the former style in the htmx documentation, remember to use kebap case when listening to htmx events using Alpine.js.
š” The @ syntax for defining event handlers is a shortcut for Alpineās more verbose
x-on
attribute so we could also have written that line as:
<input ⦠x-on:htmx:ws-after-send.window='$el.value = ""'>
That said, given htmx uses a namespace (
htmx:
) that also contains a colon, you might find the shorthand@
syntax easier to author and read.
We also specify that we want to listen to the event on the window as it is dispatched from the parent <form>
node so it would otherwise not reach the child <input>
node (events in JavaScript bubble up, not down).
š” To find out more about how Alpine.js works, make sure you read the Alpine.js documentation. Itās got a tiny API and you can likely work through the whole documentation in less than an hour.
Send a few more messages and notice that sent messages are now cleared from the input box.
Ah, thatās better! š»
Now that weāve seen how to implement two-way socket communication, letās take everything weāve learned so far and make our first peer-to-peer end-to-end encrypted Small Web app. Weāll start slowly by first building a simple ephemeral, centralised, and unauthenticated chat application and then iteratively enhance it to make it a persisted, decentralised (peer-to-peer), and end-to-end encrypted one.
Next tutorial: Streaming HTML