DjangoとHTMXで動的Web開発をシンプルに(第4回):ペイロードはどのように送る?

従来のJavaScriptのfetchを使ったAjaxリクエストでは、POSTリクエストを送信する際、通常JSON.stringify()を使ってペイロードを直接組み立てて送ります。

おおよそこのようなイメージです。

fetch("/api/todos/", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-CSRFToken": csrftoken
  },
  body: JSON.stringify({
    title: "買い物",
    done: false,
    priority: 3
  })
})

では、HTMXではどうでしょうか?一体hx-postはどのような方法でデータをサーバーに送信するのでしょうか?多くの例がごくシンプルな機能しか示していないため、複雑なペイロードを作成して送信する実践的な例を見つけるのは意外と困難です。

今回の記事では、HTMXにおけるデータ送信方法と、サーバーでそれを処理する方法を完全にまとめます。

DJANGOにおけるHTMXデータのフロー図


HTMXのデータ送信方法

fetchのペイロード感覚とhx-postのデータ送信方法はかなり異なります。

fetchでは、開発者が直接オブジェクトを作成し、JSONに変換してボディに格納します。一方、HTMXは基本的にDOMにある値を収集して送信します。

HTMXの考え方は、「JSオブジェクトを直接組み立てる」のではなく、「HTML要素から値を集めてリクエストパラメータを作成する」ことに近いです。通常、これには以下の3つの方法を使用します。

1) フォームベースで値を集めて送信する

最もHTMLらしく、Djangoにもよく合う方法です。

<form hx-post="/todos/create/" hx-target="#todo-list">
    <input type="text" name="title" placeholder="タイトル">
    <input type="number" name="priority" value="3">
    <input type="hidden" name="done" value="false">
    <button type="submit">登録</button>
</form>

この場合、HTMXはフォーム内の入力値を収集し、リクエストに含めて送信します。デフォルトのエンコーディングは、通常のフォーム送信と同じURL-encoded form data方式です。

Djangoビューでは、普段通りに次のように受け取ることができます。

def create_todo(request):
    title = request.POST.get("title")
    priority = request.POST.get("priority")
    done = request.POST.get("done")

2) フォームを使わず他の要素の値を含める:hx-include

ボタン一つにhx-postを付け、離れた場所にある特定の入力値だけを一緒に送りたい場合に使用します。

<input type="text" id="title" name="title" placeholder="タイトル">
<input type="number" id="priority" name="priority" value="3">

<button hx-post="/todos/create/"
        hx-include="#title, #priority"
        hx-target="#todo-list">
    登録
</button>

hx-includeは指定した要素の値をリクエストに含めます。わざわざ全体を<form>で囲まなくてもペイロードと似た結果を作成できるため、入力項目が少ない場合に非常に便利です。

3) 隠し値や計算された値を追加する:hx-vals

これが、fetchでペイロードオブジェクトの一部を直接作成する概念に最も近い機能です。

<button hx-post="/todos/create/"
        hx-vals='{"title": "買い物", "done": false, "priority": 3}'
        hx-target="#todo-list">
    クイック登録
</button>

hx-valsはリクエストに追加のパラメータを挿入します。デフォルトは上記のようにJSON構文を使用しますが、js:プレフィックスを付けると動的なJavaScript計算値も送信できます。

<input type="text" id="title" placeholder="タイトル">

<button hx-post="/todos/create/"
        hx-vals='js:{title: document.querySelector("#title").value, done: false, priority: 3}'
        hx-target="#todo-list">
    登録
</button>

この場合もDjangoではrequest.POSTで読み取ります。hx-vals「JSONのような構文で値を定義する」ものであり、リクエストボディ自体をJSONにするわけではないためです。


注意点:hx-valsは「JSONペイロード」とは異なります

HTMXを初めて触る際に最も混乱しやすい部分です。

<button hx-post="/my-url/" hx-vals='{"a":1, "b":2}'>

このコードは「JSONオブジェクトをbodyで送信する」という意味ではなく、リクエストパラメータにa=1&b=2を追加するという意味に近いです。つまり、サーバーでは依然としてフォームデータを受け取るように処理する必要があります。

本当にJSONボディを送りたい場合は?

もし、どうしてもfetch(... JSON.stringify(payload))のようなapplication/json形式を使用する必要がある場合は、HTMXのjson-enc拡張が必要です。

公式ドキュメントの案内に従って、次のように設定します。

<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/json-enc.js"></script>

<button hx-post="/api/todos/"
        hx-ext="json-enc"
        hx-vals='{"title": "買い物", "done": false, "priority": 3}'
        hx-target="#result">
    JSONで送信
</button>

これでようやく、私たちが慣れ親しんだJSONペイロード方式になります。このとき、Djangoビューではrequest.POSTが空になるため、request.bodyを直接読み取る必要があります。

import json

def create_todo_api(request):
    data = json.loads(request.body)
    title = data.get("title")
    # ... ロジック処理 ...
    return JsonResponse({"ok": True})

HTMXとDRFは哲学のギャップがあるが、無理に統合する必要もない

拡張パックを使ってJSONを送信することはできますが、それがHTMXらしい方法なのかという疑問は残ります。これは「データ中心(DRF)」「ハイパーメディア中心(HTMX)」という二つの哲学が衝突する点だからです。

HTMXを適切に使うと決めたのであれば、もしかしたらデータ中心の考え方から一時的に離れる必要があるかもしれません。<form>hx-includehx-valsで値を送信し、サーバーはrequest.POSTでそれを受け取り、JSONではないHTMLスニペットを返すこと。それ自体がHTMXが最も輝くポイントです。

しかし、DRFシリアライザーを諦めるのはもったいない?

DRFのシリアライザーは本当に強力です。HTMXを使うからといって、この便利なバリデーションツールを捨ててrequest.POST.get()で手動で処理しなければならないのでしょうか?

幸いなことに、DRFシリアライザーはJSONだけでなくフォームデータも非常にうまく検証してくれます。この部分は内容が長くなりそうなので、次回「HTMXとDRFシリアライザーの共存」について深く掘り下げていきたいと思います。


まとめ

今日のまとめは以下の通りです。

  1. HTMXは基本的にDOMから値を収集します。 直接ペイロードオブジェクトを構築するfetchとはアプローチが異なります。
  2. 送信方法は3種類: 全体を送る<form>、選んで送るhx-include、値を追加するhx-vals
  3. 基本はフォームデータです。 hx-valsがJSON構文を使うからといって、実際のJSONボディとして送信されるわけではないので注意してください。
  4. JSONがどうしても必要ならjson-enc拡張を使いましょう。 しかし、HTMX本来の醍醐味はHTMLの断片をやり取りすることにある、と覚えておくと良いでしょう。

次回は、DRFの利便性をHTMXに組み込む方法についてお届けします!

関連記事