PostgREST: HTML-Inhalte mit htmx bereitstellen
(postgrest.org)- Wenn PostgreSQL-Funktionen
text/htmldirekt 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_anonaufapi.todosundapi.todos_id_seqund fügt einen benutzerdefinierten Medientyp hinzu, um Anfragen mitAccept: text/htmlzu verarbeiten - Die HTML-Zusammensetzung übernimmt eine SQL-Funktion;
api.sanitize_html,api.html_todoundapi.html_all_todoskü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-valsundhx-headersaufgebaut; die Antwort-HTML ersetzt dabei#todo-list-areaoder einzelne Bearbeitungsbereiche - Diese Konfiguration ist eher ein Proof of Concept; auf der PostgREST-Seite wird derzeit an
plmustachegearbeitet, 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_anondie nötigen Rechtegrant allauf die Tabelleapi.todosgrant usage, selectauf die Sequenzapi.todos_id_seq
- Damit PostgREST Browser-Anfragen mit
Accept: text/htmlals HTML erkennt, muss ein Medientyp-Handler hinzugefügt werden- Die Domain
"text/html"wird auf Basis vontexterstellt - Dadurch kann PostgREST rohe HTML-Dokumente zurückgeben
- Die Domain
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 Listsowie die folgenden Ressourcen - Im Browser kann diese Seite unter
http://localhost:3000/rpc/indexgeö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-Entitiesapi.html_todo(api.todos): formatiert einen einzelnen To-do-Eintrag als HTML-Fragmentapi.html_all_todos(): fügt alle Einträge zu einem HTML-String zusammen und gibt bei leerer Liste die MeldungThere is nothing else to do.zurück
api.html_todoundapi.html_all_todossind interne Funktionen zum Erzeugen des Listentemplates und werden nicht direkt als PostgREST-Endpunkte verwendetapi.add_todo(_task text)fügt ein neues To-do inapi.todosein und gibt anschließend die aktualisierte HTML-Gesamtliste zurück- Die aktualisierte
api.index()enthält htmx und ein Eingabeformularhx-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_taskper POST an/rpc/add_todohx-target="#todo-list-area": setzt die Antwort-HTML in den Bereich der To-do-Liste einhx-trigger="submit": sendet die Anfrage beim Absenden des Formularshx-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/indexdie Listenanzeige und das Hinzufügen neuer To-dos prüfen
Bearbeiten, Löschen und Statusänderung auf erledigt
api.html_todowird 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 umgesetzthx-post="/rpc/change_todo_state": sendet eine AJAX-POST-Anfrage an den entsprechenden Endpunkthx-vals='{"_id": ..., "_done": ...}': fügt Anfrageparameter hinzu, statt versteckte Eingabefelder zu verwendenhx-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ückgibthx-target="#todo-edit-area-...": ersetzt nur den Bereich der einzelnen Aufgabe statt der gesamten Listehx-valsfügt Parameter für die GET-Anfrage hinzu; beim Bezug auf Tabellenspalten ist der Operatoreq.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"}'
- sendet den geänderten Aufgabennamen mit
- 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 Spaltedoneapi.change_todo_task(_id int, _task text): aktualisiert die Spaltetaskapi.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/indexTo-dos bearbeiten, löschen und als erledigt markieren
1 Kommentare
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
https://github.com/steve-chavez
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.
https://sqitch.org/
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.
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.
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.
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.
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.
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.
https://github.com/omnigres/omnigres
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.
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.
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?
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
Aber jetzt ist die Datenbank die View – haben wir dadurch wieder XSS? Das ist ein vermeidbares Problem
HTML kann mit jeder Template-Sprache gerendert werden, die diese Behandlung übernimmt