3 ポイント 投稿者 GN⁺ 2023-12-19 | 1件のコメント | WhatsAppで共有
  • PostgreSQL関数がtext/htmlを直接返すようにすれば、PostgRESTとhtmxだけでAJAXリクエストの結果を受け取り、DOMの一部を置き換えるToDoアプリを作れる
  • 例では認証を使わず、web_anonapi.todosapi.todos_id_seqの権限を付与し、Accept: text/htmlリクエストを処理するためにカスタムメディアタイプを追加する
  • HTMLの組み立てはSQL関数が担い、api.sanitize_htmlapi.html_todoapi.html_all_todosが入力値のエスケープとリストのレンダリングを処理する
  • 画面の動作はhx-posthx-gethx-targethx-triggerhx-valshx-headersで構成され、レスポンスHTMLが#todo-list-areaや個別の編集領域を置き換える
  • この構成は概念実証に近く、PostgREST側ではHTML処理を改善するためにplmustacheの作業が進められている

PostgRESTとhtmxの役割

  • PostgRESTはHTMLコンテンツを返し、htmxはAJAXリクエストの結果として受け取ったHTMLをDOM内部要素の置き換えに使用する
  • htmxはHTMLレスポンスを想定しているため、サーバーがJSONではなくHTML片を返す流れと相性がよい
  • 例は、2つの技術を一緒に使う際に可能な構成を示す概念実証である
  • PostgRESTはHTML処理面をさらに改善するためにplmustacheに取り組んでいる

ToDoアプリの準備設定

  • 例はTutorial 0 - Get it RunningをベースにしたToDoアプリである
  • 簡略化のため認証は使用せず、web_anonユーザーに必要な権限を付与する
    • api.todosテーブルにgrant all
    • api.todos_id_seqシーケンスにgrant usage, select
  • ブラウザのAccept: text/htmlリクエストをPostgRESTがHTMLとして認識するには、メディアタイプハンドラを追加する必要がある
    • "text/html"ドメインをtextとして作成する
    • この設定により、PostgRESTが生のHTMLドキュメントを返せるようになる

基本HTMLページを返す

  • api.index()関数は"text/html"を返し、基本HTMLドキュメントを生成する
  • ページにはPostgREST + HTMX To-Do Listというタイトルとともに、次のリソースが含まれる
  • ブラウザではhttp://localhost:3000/rpc/indexでこのページを開ける

リストのレンダリングとToDoの追加

  • 既存のToDoリストをHTMLにするため、3つのSQL関数が使われる
    • api.sanitize_html(text): &"><'の各文字をHTMLエンティティに置換する
    • api.html_todo(api.todos): 単一のToDo項目をHTML片としてフォーマットする
    • api.html_all_todos(): 全項目を1つのHTML文字列に結合し、項目がなければThere is nothing else to do.というメッセージを返す
  • api.html_todoapi.html_all_todosはリストテンプレートを作る内部用関数であり、PostgRESTエンドポイントとして直接は使用しない
  • api.add_todo(_task text)は新しいToDoをapi.todosに挿入したあと、更新された全リストのHTMLを返す
  • 更新後のapi.index()にはhtmxと入力フォームが含まれる
    • hx-headers='{"Accept": "text/html"}'を上位要素に入れ、下位のhtmxリクエストがHTMLレスポンスを要求するようにする
    • このヘッダーがないと、PostgRESTはリクエストをHTMLとして認識できない
  • ToDo追加フォームはhtmx属性で動作する
    • hx-post="/rpc/add_todo": _taskの入力値を/rpc/add_todoへPOSTリクエストする
    • hx-target="#todo-list-area": レスポンスHTMLをToDoリスト領域に入れる
    • hx-trigger="submit": フォーム送信時にリクエストを送る
    • hx-on="htmx:afterRequest: this.reset()": リクエスト完了後にフォームを空にする
  • 新しい関数と変更を反映するには、スキーマキャッシュをリフレッシュする必要がある
  • その後、http://localhost:3000/rpc/indexでリスト表示と新しいToDoの追加を確認できる

編集、削除、完了状態の変更

  • api.html_todoは、各項目に完了状態の変更、編集、削除UIを含むよう拡張される
  • 完了状態の変更は<form>とhtmx属性で処理する
    • hx-post="/rpc/change_todo_state": 該当エンドポイントへAJAX POSTリクエストを送る
    • hx-vals='{"_id": ..., "_done": ...}': hidden inputの代わりにリクエストパラメータを追加する
    • hx-trigger="click": 項目クリックでリクエストを実行する
  • 編集ボタンは個別のタスクを入力フィールドに変える
    • hx-get="/rpc/html_editable_task": 編集可能なHTML入力を返すエンドポイントを呼び出す
    • hx-target="#todo-edit-area-...": リスト全体ではなく個別のタスク領域だけを置き換える
    • hx-valsはGETリクエストパラメータを追加し、テーブルカラムを表すときはeq.演算子が必要になる
  • 削除ボタンはhx-post="/rpc/delete_todo"で該当ToDoの削除リクエストを送る
  • api.html_editable_task(_id int)は、特定のToDoを編集できる入力フィールドHTMLを返す
    • hx-post="/rpc/change_todo_task"で変更後のタスク名を送信する
    • hx-trigger="submit,focusout"により、送信またはフォーカスアウト時に更新する
    • hx-headers='{"Accept": "text/html"}'を含める
  • データ変更エンドポイントはいずれも、変更後に全リストのHTMLを返す
    • api.change_todo_state(_id int, _done boolean): doneカラムを更新する
    • api.change_todo_task(_id int, _task text): taskカラムを更新する
    • api.delete_todo(_id int): _idに該当するToDoを削除する
  • スキーマキャッシュをリフレッシュしたあと、http://localhost:3000/rpc/indexでToDoの編集、削除、完了処理ができる

1件のコメント

 
GN⁺ 2023-12-19
Hacker News のコメント
  • PostgREST は私が最も好きなオープンソースプロジェクトのひとつ。Supabase が10億ドル規模の会社になったのは、PostgREST と Postgres の優れた設計によるところが大きいと思う。
    Supabase がこのプロジェクトをどう支援しているのかは知らないが、かなり大規模であってほしい。少なくとも数百社の売上を生む企業が中核的な依存関係として使っているプロジェクトなのに、有料支援者が12人しかいないのを見ると、オープンソースの資金支援の現実が悲しくなる。
    https://www.patreon.com/postgrest/about

    • Supabase スタックの中核部分なので、言及のとおり、PostgREST の作業を主に行うためにリードメンテナーの Steve を雇用している。
      https://github.com/steve-chavez
    • 今は13人。とはいえ、まだ 月額 $1,529 にすぎない。
    • PostgREST が Supabase の一部だとは知らなかった。Supabase はゼロから作った模倣プロダクトだと思っていた。
    • こういう状況でいつも出てくる決まり文句がもう聞こえてくる。「ユーザーに金銭的に支援する義務はない」「嫌ならパーミッシブライセンスで公開しなければよかったのに」といった類の話だ。
      だから今は AGPLv3 かそれに近いライセンスだけを使っている。商用利用したいなら総売上の1%を払え、という形だ。
  • 概念実証としては素晴らしいし実装も称賛に値するが、些細ではない Web アプリで保守するとなると 悪夢のように見える。

    • 確かに Web サイトや 軽量アプリ向けに近い。それでもその範囲内ではかなり活用できる。
    • Sqitch で保守を楽にできるか実験している。まだハックのように感じるし、実用性には疑問が残るが、面白いし、Postgres の高度な機能をいろいろ学んでいるところだ。
      https://sqitch.org/
    • 正直、うちのメイン Web アプリケーションでこれを使ったとして、100倍簡単にならない部分が何なのか気になる。
    • 今のところはそうだと思う。あとは CI プロセスの一部として HTMX ルートをデータベースサーバーにコンパイルしてデプロイする方法があればいい。
  • こうした機能やコーディングパターンが、新しい、あるいはモダンなアプリケーションでも使われているのか気になる。
    CouchDB という JSON ドキュメントデータベースは HTTP ベースの API を提供していて、JavaScript インタプリタ内で作れる任意の形式で応答できる一覧/詳細メソッドが組み込まれていた。つまり、クライアントがデータベースを直接呼び出して HTML や JSON を受け取ることができ、サーバーは不要だった。
    しかし v1 以降はその方向の作業をやめた。保守が悪夢になるからだ。昔の PHP や ASP の1ファイルに SQL 文と HTML が混在していた良き時代を覚えている人は多いだろうが、これはあまり違って見えない。

    • 新しい世代が、昔もっともな理由で捨てられたものをまた学び直しているように思える。
      13年ほど前、個人プロジェクトでは CouchDB の Web 関連機能が本当に好きだったが、チームで扱うにはかなり不便だった。
    • モダンとは言いがたい。Oracle は25年以上前にこういうものを持っていた。
  • 私が触ったことのある唯一の PostgREST アプリはひどかった。というのも、この手の「シンプルな」フレームワークの大半がそうであるように、要件が複雑になる前までしかシンプルではないからだ。
    元の開発者たちは望む結果を得るためにデータベースにストアドプロシージャを大量に書くようになり、それがスケーラビリティの問題につながった。いつものことだが、解決策は SQL に戻ることだ。

    • そのチームは作業に合わないツールを選んだように聞こえる。エンドポイントの大半に複雑なバックエンドロジックがあるなら PostgREST を使うべきではない。
      PostgREST は CRUD アプリのために作られており、私が目にするアプリケーションの多くはこれに当てはまる。たまに必要になるカスタムのバックエンドロジックは、別サーバーやエッジ関数として立てて処理すればよい。
    • PostGREST はよく使っているが、強い ガードレールを設けている。実際の API レイヤーは常にあるべきで、これは基本的なロード処理を楽にする補助手段としてだけ使うのがよいと思う。
      こうしたツールはどれもそうだが、現実の要件に向き合うと、ツールに合わせて適応するコストが、置き換えようとしていた SQL + 何らかの言語とフレームワークより悪くなる。PostGREST もすでに複雑化しているので、こういう追加機能は自分にとって魅力を下げる。
    • ストアドプロシージャも結局は 関数形式の SQL ではないのか?
      Rails が登場して、遅いサーバーサイド言語にビジネスロジックを入れるのが良い考えだと皆に信じさせる前は、みんなこういうふうにアプリを作っていた。
  • 本当にすっきりした Web 開発スタックだ。HTML とデータベースだけで、バックエンドもフロントエンドも要らない。

    • 90年代半ばにオハイオ州の Compuserve を訪れたことがある。Web が始まったばかりの頃で、そこで会ったエンジニアの一人が、SQL ストアドプロシージャから HTML を返して作っていた音楽ストアを見せてくれた :)
    • これが格好いいと感じるなら Omnigres も見てみるとよい。
      https://github.com/omnigres/omnigres
    • 私たちはすでにこの道を通ってきた。最初はすっきりしているが、長期保守、サポート、教育には向かない。
      HTMX の輝きが薄れたら、その時の管理者は結局すべてを最初から書き直す必要があると認めることになる。
    • バックエンドとフロントエンドは依然として存在する。ただ、すべてが データベースコードの中に直接絡み合っているだけだ。
    • 結局 メインフレームに戻ってきたわけだ。今回はブラウザが端末なだけだ。
  • 以前のプロジェクトで HTMX なしに PostgREST を使ったことがあるが、どこまで押し進められるのかは印象的だった。
    HTMX とも相性はよさそうだが、SQL 関数の中で HTMX テンプレートをどれほど保守したいかはよく分からない。

    • まったく興味がない。すでに PHP で経験した。こういうパターンには 静的解析、ローカルテスト、リファクタリング、アトミックに提供される大規模変更といった重要な機能が欠けている。
      遊びで作るにはよいが、安定したものを作るには不向きだ。
  • Haskell の観点から見ると PostgREST は面白い。あまりにも当然のプロジェクトに見えるからだ。
    だが、まさにそれゆえに天才的だ。本当に Haskell らしいアイデアなので好きだ。

  • この概念を中〜大規模アプリケーションでも優れたユーザー体験を備えた保守可能なスタックにするには、どんな追加ツールが必要だと思うか?

    • 静的サイトの配信には Astro(https://astro.build) を使い、単純なデータ中心コンポーネントには PostgREST Htmx を使っている
    • 中〜大規模なら、別の抽象化レイヤー、つまり API が必要だと思う
      私も SQL の上に似たものを作ろうとしていて、技術スタックは Jinja テンプレート SQL と YML で実装した OpenAPI レイヤーだったが、それでも社内ツールの範囲に限定することになりそう。ここにある: https://jinj.at
  • 関連 Show HN: pg_render で SQL から HTML をレンダリングする
    https://news.ycombinator.com/item?id=38677852

  • このアプリに忘れないようタスクを1つ書いておきたい。私のタスクはこれ:
    この場合、task カラムがどこでもサニタイズされていないように見える

    • よくなかった昔は、ビューがデータベースと直接やり取りしていたため XSS が蔓延していた。その後、テンプレートエンジンとモデルを間に置くと、問題が解決したように見えた
      ところが今度はデータベースがビューなので、また XSS が発生しているということか? これは防げる問題だ
    • その通り。htmx はそのスクリプトを喜んで実行する。デモにも同じバグがあった: https://github.com/bigskysoftware/htmx/pull/1995/files
    • サンプルドキュメントに対するストローマン攻撃のように見える。HTMX は HTML を提供するだけで、入力は何らかの方法でサニタイズする必要がある
      HTML は、そうした処理を行う任意のテンプレート言語でレンダリングできる