3 Punkte von GN⁺ 2023-12-19 | 1 Kommentare | Auf WhatsApp teilen
  • Wenn PostgreSQL-Funktionen text/html direkt zurückgeben, lässt sich allein mit PostgREST und htmx eine To-do-App bauen, die AJAX-Ergebnisse entgegennimmt und Teile des DOM ersetzt
  • Das Beispiel vergibt ohne Authentifizierung Berechtigungen für web_anon auf api.todos und api.todos_id_seq und fügt einen benutzerdefinierten Medientyp hinzu, um Anfragen mit Accept: text/html zu verarbeiten
  • Die HTML-Zusammensetzung übernimmt eine SQL-Funktion; api.sanitize_html, api.html_todo und api.html_all_todos kümmern sich um das Escaping der Eingaben und das Rendern der Liste
  • Das Verhalten der Oberfläche wird mit hx-post, hx-get, hx-target, hx-trigger, hx-vals und hx-headers aufgebaut; die Antwort-HTML ersetzt dabei #todo-list-area oder einzelne Bearbeitungsbereiche
  • Diese Konfiguration ist eher ein Proof of Concept; auf der PostgREST-Seite wird derzeit an plmustache gearbeitet, um die HTML-Verarbeitung zu verbessern

Die Rollen von PostgREST und htmx

  • PostgREST gibt HTML-Inhalte zurück, und htmx verwendet das aus AJAX-Anfragen erhaltene HTML, um Elemente innerhalb des DOM zu ersetzen
  • Da htmx HTML-Antworten erwartet, passt es gut zu einem Ablauf, bei dem der Server statt JSON HTML-Fragmente zurückliefert
  • Das Beispiel ist ein Proof of Concept, der zeigt, welche Konfiguration möglich ist, wenn beide Technologien zusammen eingesetzt werden
  • Um die HTML-Verarbeitung weiter zu verbessern, arbeitet PostgREST an plmustache

Vorbereitung der To-do-App

  • Das Beispiel ist eine To-do-App auf Basis von Tutorial 0 - Get it Running
  • Zur Vereinfachung wird keine Authentifizierung verwendet; stattdessen erhält der Benutzer web_anon die nötigen Rechte
    • grant all auf die Tabelle api.todos
    • grant usage, select auf die Sequenz api.todos_id_seq
  • Damit PostgREST Browser-Anfragen mit Accept: text/html als HTML erkennt, muss ein Medientyp-Handler hinzugefügt werden
    • Die Domain "text/html" wird auf Basis von text erstellt
    • Dadurch kann PostgREST rohe HTML-Dokumente zurückgeben

Rückgabe der grundlegenden HTML-Seite

  • Die Funktion api.index() gibt "text/html" zurück und erzeugt das grundlegende HTML-Dokument
  • Die Seite enthält den Titel PostgREST + HTMX To-Do List sowie die folgenden Ressourcen
  • Im Browser kann diese Seite unter http://localhost:3000/rpc/index geöffnet werden

Listen-Rendering und To-do hinzufügen

  • Um die vorhandene To-do-Liste als HTML zu erzeugen, werden drei SQL-Funktionen verwendet
    • api.sanitize_html(text): ersetzt die Zeichen &, ", >, <, ' durch HTML-Entities
    • api.html_todo(api.todos): formatiert einen einzelnen To-do-Eintrag als HTML-Fragment
    • api.html_all_todos(): fügt alle Einträge zu einem HTML-String zusammen und gibt bei leerer Liste die Meldung There is nothing else to do. zurück
  • api.html_todo und api.html_all_todos sind interne Funktionen zum Erzeugen des Listentemplates und werden nicht direkt als PostgREST-Endpunkte verwendet
  • api.add_todo(_task text) fügt ein neues To-do in api.todos ein und gibt anschließend die aktualisierte HTML-Gesamtliste zurück
  • Die aktualisierte api.index() enthält htmx und ein Eingabeformular
    • hx-headers='{"Accept": "text/html"}' wird auf einem übergeordneten Element gesetzt, damit untergeordnete htmx-Anfragen HTML-Antworten anfordern
    • Ohne diesen Header erkennt PostgREST die Anfrage nicht als HTML
  • Das Formular zum Hinzufügen eines To-dos arbeitet mit htmx-Attributen
    • hx-post="/rpc/add_todo": sendet den Eingabewert _task per POST an /rpc/add_todo
    • hx-target="#todo-list-area": setzt die Antwort-HTML in den Bereich der To-do-Liste ein
    • hx-trigger="submit": sendet die Anfrage beim Absenden des Formulars
    • hx-on="htmx:afterRequest: this.reset()": leert das Formular nach Abschluss der Anfrage
  • Damit die neue Funktion und die Änderungen wirksam werden, muss der Schema-Cache aktualisiert werden
  • Danach lassen sich unter http://localhost:3000/rpc/index die Listenanzeige und das Hinzufügen neuer To-dos prüfen

Bearbeiten, Löschen und Statusänderung auf erledigt

  • api.html_todo wird erweitert, sodass jeder Eintrag eine UI zum Umschalten des Erledigt-Status, zum Bearbeiten und zum Löschen enthält
  • Das Umschalten des Erledigt-Status wird mit <form> und htmx-Attributen umgesetzt
    • hx-post="/rpc/change_todo_state": sendet eine AJAX-POST-Anfrage an den entsprechenden Endpunkt
    • hx-vals='{"_id": ..., "_done": ...}': fügt Anfrageparameter hinzu, statt versteckte Eingabefelder zu verwenden
    • hx-trigger="click": löst die Anfrage beim Klick auf den Eintrag aus
  • Die Schaltfläche zum Bearbeiten verwandelt die einzelne Aufgabe in ein Eingabefeld
    • hx-get="/rpc/html_editable_task": ruft den Endpunkt auf, der editierbares HTML für die Eingabe zurückgibt
    • hx-target="#todo-edit-area-...": ersetzt nur den Bereich der einzelnen Aufgabe statt der gesamten Liste
    • hx-vals fügt Parameter für die GET-Anfrage hinzu; beim Bezug auf Tabellenspalten ist der Operator eq. erforderlich
  • Die Schaltfläche zum Löschen sendet mit hx-post="/rpc/delete_todo" eine Anfrage zum Entfernen des betreffenden To-dos
  • api.html_editable_task(_id int) gibt HTML für ein Eingabefeld zurück, mit dem ein bestimmtes To-do bearbeitet werden kann
    • sendet den geänderten Aufgabennamen mit hx-post="/rpc/change_todo_task"
    • aktualisiert bei hx-trigger="submit,focusout" beim Absenden oder beim Verlassen des Fokus
    • enthält hx-headers='{"Accept": "text/html"}'
  • Alle Endpunkte, die Daten ändern, geben nach der Änderung die vollständige HTML-Liste zurück
    • api.change_todo_state(_id int, _done boolean): aktualisiert die Spalte done
    • api.change_todo_task(_id int, _task text): aktualisiert die Spalte task
    • api.delete_todo(_id int): löscht das To-do mit der passenden _id
  • Nach dem Aktualisieren des Schema-Caches lassen sich unter http://localhost:3000/rpc/index To-dos bearbeiten, löschen und als erledigt markieren

1 Kommentare

 
GN⁺ 2023-12-19
Meinungen auf Hacker News
  • PostgREST ist eines meiner liebsten Open-Source-Projekte. Dass Supabase zu einem Unternehmen im Milliarden-Dollar-Bereich geworden ist, liegt meiner Meinung nach zu einem großen Teil am hervorragenden Design von PostgREST und Postgres.
    Ich weiß nicht, wie Supabase dieses Projekt sponsert, aber ich hoffe, in sehr großem Umfang. Es ist traurig zu sehen, wie Open-Source-Finanzierung in der Realität aussieht, wenn ein Projekt, das mindestens Hunderte umsatzgenerierende Unternehmen als zentrale Abhängigkeit nutzen, nur 12 zahlende Unterstützer hat.
    https://www.patreon.com/postgrest/about

    • Da es ein zentraler Teil des Supabase-Stacks ist, beschäftigt Supabase, wie erwähnt, den Lead-Maintainer Steve, damit er hauptsächlich an PostgREST arbeitet.
      https://github.com/steve-chavez
    • Jetzt sind es 13. Trotzdem sind es erst 1.529 $ pro Monat.
    • Ich wusste nicht, dass PostgREST Teil von Supabase ist. Ich dachte, Supabase sei ein von Grund auf neu gebautes Nachahmerprodukt.
    • Ich höre schon die üblichen Sprüche, die in solchen Situationen immer kommen: „Die Nutzer sind nicht verpflichtet, dich finanziell zu unterstützen“, „Wenn dir das nicht gefällt, hättest du es nicht unter einer permissiven Lizenz veröffentlichen sollen“ und dergleichen.
      Deshalb nutze ich inzwischen nur noch AGPLv3 oder ähnliche Lizenzen. Nach dem Motto: Wenn ihr es kommerziell nutzen wollt, zahlt 1 % eures Bruttoumsatzes.
  • Als Proof of Concept ist das cool, und die Implementierung ist lobenswert, aber für die Wartung in einer nicht trivialen Web-App sieht es nach einem Albtraum aus.

    • Definitiv eher etwas für Websites oder leichtgewichtige Apps. Innerhalb dieses Rahmens kann man es aber ziemlich intensiv nutzen.
    • Ich experimentiere gerade damit, ob man die Wartung mit Sqitch einfacher machen kann. Es fühlt sich immer noch wie ein Hack an, und ich habe Zweifel an der Praxistauglichkeit, aber es macht Spaß, und ich lerne dabei mehrere fortgeschrittene Postgres-Funktionen.
      https://sqitch.org/
    • Ehrlich gesagt frage ich mich, welcher Teil unserer Haupt-Webanwendung nicht 100-mal einfacher würde, wenn wir das dafür verwenden würden.
    • Im Moment sehe ich das auch so. Jetzt bräuchte man nur noch eine Möglichkeit, HTMX-Routen als Teil des CI-Prozesses auf den Datenbankserver zu kompilieren und zu deployen.
  • Ich frage mich, ob solche Funktionen oder Coding-Patterns neu sind oder auch in modernen Anwendungen verwendet werden.
    CouchDB, eine JSON-Dokumentendatenbank, bot eine HTTP-basierte API und hatte eingebaute Listen-/Detail-Methoden, die in jedem Format antworten konnten, das man in einem JavaScript-Interpreter erzeugen konnte. Das heißt, der Client konnte direkt die Datenbank aufrufen und HTML oder JSON zurückbekommen, ganz ohne Server.
    Nach v1 wurde die Arbeit in diese Richtung jedoch eingestellt, weil die Wartung zum Albtraum wurde. Viele erinnern sich sicher an die guten alten Zeiten, als in einer einzigen PHP- oder ASP-Datei SQL-Statements und HTML vermischt waren; das hier sieht nicht besonders anders aus.

    • Es wirkt so, als würde eine neue Generation Dinge wieder neu lernen, die früher aus guten Gründen aufgegeben wurden.
      Vor etwa 13 Jahren mochte ich die Web-Funktionen von CouchDB in einem persönlichen Projekt wirklich sehr, aber im Team waren sie ziemlich unbequem zu handhaben.
    • Als modern würde ich das kaum bezeichnen. Oracle hatte so etwas schon vor mehr als 25 Jahren.
  • Die einzige PostgREST-App, an der ich gearbeitet habe, war furchtbar. Denn wie bei den meisten dieser „einfachen“ Frameworks gilt: Sie sind nur so lange einfach, bis die Anforderungen komplex werden.
    Die ursprünglichen Autoren endeten damit, Unmengen an Stored Procedures in die Datenbank zu schreiben, um das gewünschte Ergebnis zu bekommen, was zu Skalierbarkeitsproblemen führte. Wie immer besteht die Lösung darin, zu SQL zurückzukehren.

    • Klingt so, als hätte das Team ein Werkzeug gewählt, das nicht zur Aufgabe passte. Wenn die meisten Endpoints komplexe Backend-Logik haben, sollte man PostgREST nicht verwenden.
      PostgREST wurde für CRUD-Apps gebaut, und viele der Anwendungen, mit denen ich zu tun habe, fallen in diese Kategorie. Gelegentlich benötigte maßgeschneiderte Backend-Logik kann man über einen separaten Server oder Edge Functions abwickeln.
    • Ich nutze PostGREST viel, setze aber starke Guardrails. Meiner Meinung nach sollte es immer eine echte API-Schicht geben, und das hier eignet sich vor allem als Hilfsmittel, um einfaches Laden bequemer zu machen.
      Wie bei all diesen Tools werden die Kosten, sich bei realen Anforderungen an das Tool anzupassen, schlimmer als das, was man eigentlich ersetzen wollte: SQL plus irgendeine Sprache und ein Framework. PostGREST wird ohnehin schon komplexer, und solche Zusatzfunktionen machen es für mich weniger attraktiv.
    • Sind Stored Procedures am Ende nicht auch einfach SQL in Funktionsform?
      Bevor Rails aufkam und alle glauben machte, es sei eine gute Idee, Business-Logik in langsame serverseitige Sprachen zu packen, haben alle Apps auf diese Weise gebaut.
  • Ein wirklich sauberer Webentwicklungs-Stack: nur HTML und Datenbank, kein Backend und kein Frontend nötig.

    • Mitte der 90er habe ich Compuserve in Ohio besucht. Das Web stand gerade am Anfang, und ein Ingenieur dort zeigte mir einen Musikladen, den er baute und der HTML aus SQL Stored Procedures zurückgab. :)
    • Wenn dir das cool vorkommt, lohnt sich auch ein Blick auf Omnigres.
      https://github.com/omnigres/omnigres
    • Diesen Weg sind wir schon gegangen. Anfangs ist es sauber, aber für langfristige Wartung, Support und Schulung ist es nicht gut.
      Wenn der Glanz von HTMX verblasst ist, wird der damalige Maintainer am Ende zugeben müssen, dass alles von Grund auf neu geschrieben werden muss.
    • Backend und Frontend gibt es weiterhin. Sie sind nur komplett direkt im Datenbankcode miteinander verheddert.
    • Am Ende sind wir wieder beim Mainframe angekommen. Diesmal ist der Browser nur das Terminal.
  • Ich habe in einem früheren Projekt PostgREST ohne HTMX ausprobiert und war beeindruckt, wie weit man es treiben kann.
    HTMX scheint auch gut dazu zu passen, aber ich bin mir nicht sicher, wie sehr ich HTMX-Templates in SQL-Funktionen warten möchte.

    • Überhaupt kein Interesse. Das habe ich schon mit PHP durch. Bei solchen Patterns fehlen wichtige Dinge wie statische Analyse, lokale Tests, Refactoring und atomar ausgelieferte große Änderungen.
      Zum Herumspielen ist es gut, aber um etwas Zuverlässiges zu bauen, ungeeignet.
  • Aus Haskell-Perspektive ist PostgREST interessant, weil es wie ein allzu offensichtliches Projekt wirkt.
    Aber genau deshalb ist es genial. Es ist eine wirklich Haskell-typische Idee, und das gefällt mir.

  • Welche zusätzlichen Tools hältst du für nötig, um dieses Konzept auch bei mittelgroßen bis großen Anwendungen zu einem wartbaren Stack mit guter User Experience zu machen?

    • Für das Bereitstellen statischer Sites verwende ich Astro(https://astro.build), und für einfache datengetriebene Komponenten nutze ich PostgREST Htmx
    • Bei mittelgroßer bis großer Skalierung braucht es meiner Ansicht nach eine eigene Abstraktionsschicht, also eine API
      Ich habe ebenfalls versucht, etwas Ähnliches auf SQL aufzubauen; der Tech-Stack war eine mit Jinja-Template-SQL und YML implementierte OpenAPI-Schicht, aber ich würde das trotzdem wohl auf interne Tools beschränken. Hier ist es: https://jinj.at
  • Zugehöriges Show HN: HTML aus SQL mit pg_render rendern
    https://news.ycombinator.com/item?id=38677852

  • Ich möchte in dieser App eine Aufgabe notieren, damit ich sie nicht vergesse. Meine Aufgabe ist diese:
    In diesem Fall scheint die task-Spalte nirgends bereinigt zu werden

    • In den schlechten alten Zeiten war XSS allgegenwärtig, weil Views direkt mit der Datenbank gesprochen haben. Dann stellte man Template-Engines und Modelle dazwischen, und das Problem schien gelöst
      Aber jetzt ist die Datenbank die View – haben wir dadurch wieder XSS? Das ist ein vermeidbares Problem
    • Stimmt. htmx führt dieses Skript bereitwillig aus. Die Demo hatte denselben Bug: https://github.com/bigskysoftware/htmx/pull/1995/files
    • Das wirkt wie ein Strohmann-Angriff auf die Beispiel-Dokumentation. HTMX liefert lediglich HTML aus; Eingaben müssen so oder so bereinigt werden
      HTML kann mit jeder Template-Sprache gerendert werden, die diese Behandlung übernimmt