htmxでWebSocket通信を簡単に行う「hx-ws」属性の解説


htmxは、HTML属性だけでAJAX通信やWebSocketsとの双方向通信を実現できるライブラリです。「hx-ws」属性は、このWebSockets機能を司る重要な要素の一つです。本記事では、「hx-ws」属性の役割と基本的な構文、そして具体的なユースケースを交えて、そのプログラミングについて分かりやすく解説します。

「hx-ws」属性の役割

「hx-ws」属性は、クライアントサイドからWebSocketサーバーへの接続と、サーバーとのメッセージ送受信を可能にします。具体的には、以下の機能を実現します。

  • メッセージ受信
    サーバーから受信したメッセージを、指定された要素に挿入したり、イベントをトリガーしたりすることができます。
  • メッセージ送信
    属性のサブ属性を用いて、サーバーへ送信するメッセージの内容を定義できます。
  • WebSocketサーバーへの接続確立
    属性値にWebSocketサーバーのURLを指定することで、クライアントとサーバー間で双方向の通信路を確立します。

基本的な構文

「hx-ws」属性の基本的な構文は以下の通りです。

hx-ws="接続先URL [オプション属性]"
  • オプション属性
    任意。「hx-headers」、「hx-params」などの属性を用いて、接続やメッセージングに関する詳細な設定を指定できます。
  • 接続先URL
    必須。WebSocketサーバーのURLを指定します。

具体的なユースケース

1 チャットアプリケーション

以下は、シンプルなチャットアプリケーションの例です。この例では、ユーザーがメッセージを入力して送信すると、そのメッセージがWebSocketサーバーに送信され、サーバーからブロードキャストされたメッセージは他のユーザーに表示されます。

<input type="text" id="message-input">

<button hx-ws="send: /chat/message" hx-headers="Content-Type: application/json" hx-target="#message-input">送信</button>

<div id="chat-messages"></div>

<script>
  // メッセージ送信時の処理
  const sendMessage = () => {
    const message = document.getElementById('message-input').value;
    const messageJSON = JSON.stringify({ message });
    document.documentElement.setAttribute('hx-ws', `send: /chat/message, data: ${messageJSON}`);
  };

  // メッセージ受信時の処理
  document.addEventListener('DOMContentLoaded', () => {
    document.documentElement.addEventListener('hx-ws-message', (event) => {
      const messageData = JSON.parse(event.detail.data);
      const newMessage = document.createElement('div');
      newMessage.textContent = messageData.message;
      document.getElementById('chat-messages').appendChild(newMessage);
    });
  });
</script>

2 リアルタイムな株価情報更新

以下は、WebSocketを用いてリアルタイムに株価情報を更新する例です。この例では、ユーザーが銘柄シンボルを入力すると、その銘柄の最新株価がWebSocketサーバーから受信され、ページに表示されます。

<input type="text" id="stock-symbol">

<div id="stock-price"></div>

<script>
  // 銘柄シンボル変更時の処理
  document.getElementById('stock-symbol').addEventListener('input', () => {
    const symbol = document.getElementById('stock-symbol').value;
    document.documentElement.setAttribute('hx-ws', `connect: /stocks/${symbol}`);
  });

  // 株価情報受信時の処理
  document.addEventListener('DOMContentLoaded', () => {
    document.documentElement.addEventListener('hx-ws-message', (event) => {
      const stockData = JSON.parse(event.detail.data);
      document.getElementById('stock-price').textContent = `${stockData.symbol}: ${stockData.price}`;
    });
  });
</script>


  1. チャットアプリケーション
  2. リアルタイムな株価情報更新
  3. カウントアップタイマー

チャットアプリケーション

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>チャットアプリケーション</title>
  <script src="https://htmx.org/docs/"></script>
</head>
<body>
  <h1>チャット</h1>

  <input type="text" id="message-input" placeholder="メッセージを入力">

  <button hx-ws="send: /chat/message" hx-headers="Content-Type: application/json" hx-target="#message-input">送信</button>

  <div id="chat-messages"></div>

  <script>
    // メッセージ送信時の処理
    const sendMessage = () => {
      const message = document.getElementById('message-input').value;
      const messageJSON = JSON.stringify({ message });
      document.documentElement.setAttribute('hx-ws', `send: /chat/message, data: ${messageJSON}`);
    };

    // メッセージ受信時の処理
    document.addEventListener('DOMContentLoaded', () => {
      document.documentElement.addEventListener('hx-ws-message', (event) => {
        const messageData = JSON.parse(event.detail.data);
        const newMessage = document.createElement('div');
        newMessage.textContent = `${messageData.user}: ${messageData.message}`;
        document.getElementById('chat-messages').appendChild(newMessage);
      });
    });
  </script>
</body>
</html>

解説

  • イベントリスナー内では、受信したメッセージを解析し、DOMに新しいメッセージ要素を追加しています。
  • JavaScript部分では、sendMessage関数を使ってメッセージを送信し、DOMContentLoadedイベントでhx-ws-messageイベントリスナーを登録しています。
  • hx-target属性を使って、メッセージ送信後にフォーカスを戻す要素を指定しています。
  • hx-headers属性を使って、送信するメッセージのContent-Typeをapplication/jsonに設定しています。
  • このコードでは、WebSocketサーバーのURLを/chat/messageに設定しています。

リアルタイムな株価情報更新

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>株価情報更新</title>
  <script src="https://htmx.org/docs/"></script>
</head>
<body>
  <h1>株価情報</h1>

  <input type="text" id="stock-symbol" placeholder="銘柄シンボルを入力">

  <div id="stock-price"></div>

  <script>
    // 銘柄シンボル変更時の処理
    document.getElementById('stock-symbol').addEventListener('input', () => {
      const symbol = document.getElementById('stock-symbol').value;
      document.documentElement.setAttribute('hx-ws', `connect: /stocks/${symbol}`);
    });

    // 株価情報受信時の処理
    document.addEventListener('DOMContentLoaded', () => {
      document.documentElement.addEventListener('hx-ws-message', (event) => {
        const stockData = JSON.parse(event.detail.data);
        document.getElementById('stock-price').textContent = `${stockData.symbol}: ${stockData.price}`;
      });
    });
  </script>
</body>
</html>
  • JavaScript部分では、inputイベントで銘柄シンボルが変更された時にconnectリクエストを送信し、DOMContentLoadedイベントでhx-ws-messageイベントリスナーを登録しています
  • {symbol}部分は、入力された銘柄シンボルに置き換えられます。
  • このコードでは、WebSocketサーバーのURLを/stocks/{symbol}に設定しています。


Fetch API

Fetch APIは、JavaScriptで非同期HTTPリクエストを行うための標準APIです。WebSocketとは異なり、双方向のリアルタイム通信には対応していませんが、シンプルなデータのやり取りであれば、Fetch APIの方が軽量で扱いやすい場合があります。

<button onclick="fetchStockPrice()">株価取得</button>

<div id="stock-price"></div>

<script>
  function fetchStockPrice() {
    const symbol = document.getElementById('stock-symbol').value;
    const url = `/stocks/${symbol}`;

    fetch(url)
      .then(response => response.json())
      .then(data => {
        document.getElementById('stock-price').textContent = `${data.symbol}: ${data.price}`;
      });
  }
</script>

Server-Sent Events (SSE)

SSEは、サーバーからクライアントへリアルタイムにデータを送信するための技術です。WebSocketとは異なり、クライアントからサーバーへメッセージを送信することはできませんが、サーバーからの一方的なデータ配信には適しています。

<div id="stock-price"></div>

<script>
  const eventSource = new EventSource('/stocks/stream');

  eventSource.addEventListener('message', (event) => {
    const stockData = JSON.parse(event.data);
    document.getElementById('stock-price').textContent = `${stockData.symbol}: ${stockData.price}`;
  });
</script>

htmx以外にも、WebSocketを扱うためのライブラリは多数存在します。例えば、以下のライブラリは、htmxよりも高度な機能や柔軟性を提供する場合があります。

選択の指針

どの方法を選択するかは、以下の要素を考慮する必要があります。

  • 開発者の経験
    特定のライブラリに対する経験や知識
  • パフォーマンス
    軽量でシンプルな方法が必要かどうか、高度な機能が必要かどうか
  • 必要な機能
    双方向のリアルタイム通信が必要かどうか、サーバーからの一方的なデータ配信のみで十分かどうか