3 điểm bởi GN⁺ 2023-12-19 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nếu một hàm PostgreSQL trả trực tiếp text/html, bạn có thể tạo ứng dụng to-do chỉ với PostgREST và htmx, nhận kết quả từ yêu cầu AJAX rồi thay thế một phần DOM
  • Ví dụ cấp quyền cho web_anon đối với api.todosapi.todos_id_seq mà không cần xác thực, đồng thời thêm kiểu media tùy chỉnh để xử lý các yêu cầu Accept: text/html
  • Việc ghép HTML do các hàm SQL đảm nhiệm; api.sanitize_html, api.html_todo, api.html_all_todos xử lý escape giá trị đầu vào và render danh sách
  • Hành vi trên giao diện được cấu hình bằng hx-post, hx-get, hx-target, hx-trigger, hx-vals, hx-headers; HTML phản hồi sẽ thay thế #todo-list-area hoặc từng vùng chỉnh sửa riêng lẻ
  • Cấu hình này gần với một bằng chứng khái niệm, và phía PostgREST đang phát triển plmustache để cải thiện việc xử lý HTML

Vai trò của PostgREST và htmx

  • PostgREST trả về nội dung HTML, còn htmx dùng HTML nhận được từ kết quả yêu cầu AJAX để thay thế các phần tử bên trong DOM
  • Vì htmx kỳ vọng phản hồi HTML, nó rất phù hợp với luồng trong đó server trả về các mảnh HTML thay vì JSON
  • Ví dụ này là một bằng chứng khái niệm cho thấy cấu hình có thể thực hiện khi dùng hai công nghệ này cùng nhau
  • PostgREST đang phát triển plmustache để cải thiện hơn nữa khía cạnh xử lý HTML

Thiết lập chuẩn bị cho ứng dụng to-do

  • Ví dụ là một ứng dụng to-do dựa trên Tutorial 0 - Get it Running
  • Để đơn giản hóa, không sử dụng xác thực và cấp các quyền cần thiết cho người dùng web_anon
    • grant all trên bảng api.todos
    • grant usage, select trên sequence api.todos_id_seq
  • Để PostgREST nhận diện yêu cầu Accept: text/html từ trình duyệt là HTML, cần thêm media type handler
    • Tạo domain "text/html" dưới dạng text
    • Thiết lập này cho phép PostgREST trả về tài liệu HTML thô

Trả về trang HTML cơ bản

  • Hàm api.index() trả về "text/html" và tạo tài liệu HTML cơ bản
  • Trang bao gồm tiêu đề PostgREST + HTMX To-Do List cùng các tài nguyên sau
  • Trên trình duyệt, có thể mở trang này tại http://localhost:3000/rpc/index

Render danh sách và thêm to-do

  • Ba hàm SQL được dùng để tạo danh sách to-do hiện có dưới dạng HTML
    • api.sanitize_html(text): thay thế các ký tự &, ", >, <, ' bằng HTML entity
    • api.html_todo(api.todos): định dạng một mục to-do đơn lẻ thành mảnh HTML
    • api.html_all_todos(): ghép toàn bộ các mục thành một chuỗi HTML; nếu không có mục nào thì trả về thông báo There is nothing else to do.
  • api.html_todoapi.html_all_todos là các hàm dùng nội bộ để tạo template danh sách, không được dùng trực tiếp làm endpoint PostgREST
  • api.add_todo(_task text) chèn một to-do mới vào api.todos, rồi trả về HTML danh sách đầy đủ đã cập nhật
  • api.index() đã cập nhật bao gồm htmx và form nhập liệu
    • Đặt hx-headers='{"Accept": "text/html"}' ở phần tử cha để các yêu cầu htmx con yêu cầu phản hồi HTML
    • Nếu không có header này, PostgREST sẽ không nhận diện yêu cầu là HTML
  • Form thêm to-do hoạt động bằng các thuộc tính htmx
    • hx-post="/rpc/add_todo": gửi yêu cầu POST giá trị nhập _task tới /rpc/add_todo
    • hx-target="#todo-list-area": đưa HTML phản hồi vào vùng danh sách to-do
    • hx-trigger="submit": gửi yêu cầu khi submit form
    • hx-on="htmx:afterRequest: this.reset()": xóa form sau khi yêu cầu hoàn tất
  • Cần làm mới schema cache để phản ánh hàm mới và các thay đổi
  • Sau đó, có thể kiểm tra việc hiển thị danh sách và thêm to-do mới tại http://localhost:3000/rpc/index

Chỉnh sửa, xóa và thay đổi trạng thái hoàn thành

  • api.html_todo được mở rộng để mỗi mục bao gồm UI đổi trạng thái hoàn thành, chỉnh sửa và xóa
  • Việc đổi trạng thái hoàn thành được xử lý bằng <form> và các thuộc tính htmx
    • hx-post="/rpc/change_todo_state": gửi yêu cầu AJAX POST tới endpoint tương ứng
    • hx-vals='{"_id": ..., "_done": ...}': thêm tham số yêu cầu thay vì dùng input ẩn
    • hx-trigger="click": thực thi yêu cầu khi nhấp vào mục
  • Nút chỉnh sửa biến từng tác vụ thành trường nhập liệu
    • hx-get="/rpc/html_editable_task": gọi endpoint trả về input HTML có thể chỉnh sửa
    • hx-target="#todo-edit-area-...": chỉ thay thế vùng tác vụ riêng lẻ thay vì toàn bộ danh sách
    • hx-vals thêm tham số cho yêu cầu GET; khi biểu thị cột bảng, cần toán tử eq.
  • Nút xóa gửi yêu cầu xóa to-do tương ứng bằng hx-post="/rpc/delete_todo"
  • api.html_editable_task(_id int) trả về HTML trường nhập liệu để chỉnh sửa một to-do cụ thể
    • Gửi tên tác vụ đã thay đổi bằng hx-post="/rpc/change_todo_task"
    • Cập nhật khi submit hoặc mất focus bằng hx-trigger="submit,focusout"
    • Bao gồm hx-headers='{"Accept": "text/html"}'
  • Tất cả endpoint thay đổi dữ liệu đều trả về HTML danh sách đầy đủ sau khi thay đổi
    • api.change_todo_state(_id int, _done boolean): cập nhật cột done
    • api.change_todo_task(_id int, _task text): cập nhật cột task
    • api.delete_todo(_id int): xóa to-do tương ứng với _id
  • Sau khi làm mới schema cache, có thể chỉnh sửa, xóa và đánh dấu hoàn thành to-do tại http://localhost:3000/rpc/index

1 bình luận

 
GN⁺ 2023-12-19
Ý kiến trên Hacker News
  • PostgREST là một trong những dự án mã nguồn mở mình thích nhất. Mình nghĩ việc Supabase trở thành công ty trị giá 1 tỷ USD phần lớn là nhờ thiết kế tuyệt vời của PostgREST và Postgres
    Mình không biết Supabase tài trợ cho dự án này như thế nào, nhưng hy vọng là ở quy mô rất lớn. Thật buồn khi nhìn vào thực tế tài trợ cho mã nguồn mở: một dự án được ít nhất hàng trăm công ty có doanh thu dùng làm phụ thuộc cốt lõi mà chỉ có 12 người tài trợ trả phí
    https://www.patreon.com/postgrest/about

    • Vì đây là phần cốt lõi của stack Supabase, như đã nói, họ đang thuê maintainer chính Steve để chủ yếu làm việc trên PostgREST
      https://github.com/steve-chavez
    • Giờ là 13 người rồi. Dù vậy vẫn chỉ 1.529 USD/tháng
    • Không biết PostgREST là một phần của Supabase. Mình tưởng Supabase là một sản phẩm bắt chước được làm lại từ đầu
    • Mình đã nghe thấy những câu sáo rỗng lúc nào cũng xuất hiện trong tình huống này rồi. Kiểu như “người dùng không có nghĩa vụ hỗ trợ bạn về tiền bạc”, “nếu không thích thì đáng lẽ đừng phát hành dưới giấy phép permissive”
      Vì vậy giờ mình chỉ dùng AGPLv3 hoặc giấy phép tương tự. Kiểu nếu muốn dùng thương mại thì trả 1% tổng doanh thu
  • Là một bản chứng minh khái niệm thì rất hay và phần triển khai cũng đáng khen, nhưng để bảo trì trong một web app không hề tầm thường thì trông như ác mộng

    • Chắc chắn nó hợp với website hoặc ứng dụng nhẹ hơn. Nhưng trong phạm vi đó thì vẫn có thể tận dụng khá nhiều
    • Hiện mình đang thử xem có thể dùng Sqitch để làm cho việc bảo trì dễ hơn không. Vẫn có cảm giác như hack và mình còn nghi ngờ khả năng dùng thực tế, nhưng khá thú vị và đang học được nhiều tính năng nâng cao của Postgres
      https://sqitch.org/
    • Nói thật là mình tò mò không biết phần nào trong ứng dụng web chính của chúng tôi sẽ không dễ hơn 100 lần nếu dùng cái này
    • Hiện tại mình nghĩ là vậy. Giờ chỉ cần có cách biên dịch và triển khai các route HTMX lên máy chủ cơ sở dữ liệu như một phần của quy trình CI là được
  • Mình tò mò liệu những tính năng hoặc mẫu lập trình như thế này có mới không, hoặc có được dùng trong các ứng dụng hiện đại không
    CouchDB, một cơ sở dữ liệu tài liệu JSON, từng cung cấp API dựa trên HTTP và có sẵn các phương thức list/detail có thể trả về bất kỳ định dạng nào tạo được bên trong trình thông dịch JavaScript. Tức là client có thể gọi trực tiếp cơ sở dữ liệu để nhận HTML hoặc JSON, không cần server
    Nhưng sau v1 họ đã dừng hướng đó, vì việc bảo trì trở thành ác mộng. Chắc nhiều người còn nhớ “ngày xưa tốt đẹp” khi trong một file PHP hoặc ASP vừa có câu lệnh SQL vừa có HTML; cái này trông chẳng khác mấy

    • Có vẻ thế hệ mới đang học lại những thứ từng bị bỏ vì những lý do xác đáng
      Khoảng 13 năm trước, trong một dự án cá nhân, mình thật sự thích các tính năng liên quan đến web của CouchDB, nhưng với một team thì xử lý khá bất tiện
    • Khó mà gọi là hiện đại. Oracle đã có thứ như thế này hơn 25 năm trước rồi
  • Ứng dụng PostgREST duy nhất mình từng làm việc cùng thật kinh khủng. Vì cũng như đa số các framework “đơn giản” kiểu này, chúng chỉ đơn giản cho đến khi yêu cầu trở nên phức tạp
    Những tác giả ban đầu cuối cùng phải dùng cả đống stored procedure trong cơ sở dữ liệu để đạt kết quả mong muốn, và điều đó dẫn đến vấn đề về khả năng mở rộng. Như mọi khi, giải pháp là quay lại với SQL

    • Nghe như team đó đã chọn công cụ không phù hợp với công việc. Nếu hầu hết endpoint đều có logic backend phức tạp thì không nên dùng PostgREST
      PostgREST được tạo ra cho ứng dụng CRUD, và nhiều ứng dụng mình gặp thuộc loại này. Logic backend tùy chỉnh thỉnh thoảng cần đến thì có thể xử lý bằng cách chạy server riêng hoặc edge function
    • Mình dùng PostGREST khá nhiều, nhưng đặt guardrail mạnh. Mình nghĩ lúc nào cũng nên có một tầng API thực sự, còn cái này chỉ nên dùng như một phương tiện phụ trợ để việc tải dữ liệu cơ bản thuận tiện hơn
      Cũng như mọi công cụ kiểu này, khi gặp yêu cầu thực tế, chi phí thích nghi theo công cụ còn tệ hơn thứ mà nó định thay thế, tức SQL + một ngôn ngữ và framework nào đó. PostGREST hiện cũng đã trở nên phức tạp rồi, nên những tính năng bổ sung như thế này làm nó kém hấp dẫn hơn với mình
    • Stored procedure rốt cuộc chẳng phải cũng là SQL dưới dạng hàm sao?
      Trước khi Rails xuất hiện và khiến mọi người tin rằng đưa business logic vào các ngôn ngữ phía server chậm chạp là ý hay, ai cũng xây app theo cách này
  • Đây là một stack phát triển web thật sự gọn gàng. Chỉ có HTML và cơ sở dữ liệu, không cần backend và frontend

    • Giữa thập niên 90 mình từng ghé thăm Compuserve ở Ohio. Đó là lúc web mới bắt đầu, và một kỹ sư mình gặp ở đó đã cho mình xem một cửa hàng nhạc mà anh ấy đang xây bằng cách trả về HTML từ SQL stored procedure :)
    • Nếu thấy cái này hay thì cũng đáng xem Omnigres
      https://github.com/omnigres/omnigres
    • Chúng ta đã đi qua con đường này rồi. Ban đầu thì gọn gàng, nhưng không tốt cho bảo trì dài hạn, hỗ trợ và đào tạo
      Khi ánh hào quang của HTMX phai đi, người bảo trì lúc đó cuối cùng sẽ phải thừa nhận rằng cần viết lại tất cả từ đầu
    • Backend và frontend vẫn tồn tại. Chỉ là tất cả bị trộn trực tiếp vào trong mã cơ sở dữ liệu mà thôi
    • Rốt cuộc là quay lại mainframe. Lần này trình duyệt chỉ là terminal
  • Trong một dự án cũ, mình đã dùng PostgREST mà không có HTMX, và khá ấn tượng với việc có thể đẩy nó đi xa đến mức nào
    HTMX cũng có vẻ hợp, nhưng mình không chắc mình muốn bảo trì template HTMX bên trong các hàm SQL đến mức nào

    • Hoàn toàn không hứng thú. Mình đã trải qua chuyện này với PHP rồi. Mẫu này thiếu những khả năng quan trọng như phân tích tĩnh, kiểm thử cục bộ, refactor, và các thay đổi lớn được chuyển giao một cách nguyên tử
      Làm để nghịch thì tốt, nhưng không phù hợp để xây thứ ổn định
  • Nhìn từ góc độ Haskell, PostgREST rất thú vị. Vì nó trông như một dự án quá hiển nhiên
    Nhưng chính vì vậy mà nó thiên tài. Mình thích vì đó là một ý tưởng rất đúng chất Haskell

  • Theo bạn, cần thêm những công cụ nào để biến khái niệm này thành một stack có thể bảo trì với trải nghiệm người dùng tốt cho các ứng dụng quy mô vừa và lớn?

    • Tôi dùng Astro(https://astro.build) để phục vụ site tĩnh, và dùng PostgREST Htmx cho các component đơn giản, thiên về dữ liệu.
    • Với quy mô vừa và lớn, tôi nghĩ cần một lớp trừu tượng riêng, tức là API.
      Tôi cũng từng định xây một thứ tương tự trên SQL; stack kỹ thuật là một lớp OpenAPI được triển khai bằng SQL template Jinja và YML, nhưng có lẽ vẫn sẽ giới hạn trong phạm vi công cụ nội bộ. Nó ở đây: https://jinj.at
  • Show HN liên quan: render HTML từ SQL bằng pg_render
    https://news.ycombinator.com/item?id=38677852

  • Tôi muốn ghi lại một tác vụ trong app này để khỏi quên. Tác vụ của tôi là:
    Trong trường hợp này, có vẻ cột task không được làm sạch ở đâu cả.

    • Ngày xưa tệ hại, vì view nói chuyện trực tiếp với database nên XSS tràn lan. Sau đó, khi đặt template engine và model ở giữa, vấn đề tưởng như đã được giải quyết.
      Nhưng giờ database lại là view, nên XSS quay lại sao? Đây là vấn đề có thể ngăn chặn được.
    • Đúng vậy. htmx sẵn sàng thực thi script đó. Demo cũng từng có cùng lỗi: https://github.com/bigskysoftware/htmx/pull/1995/files
    • Trông giống như một lập luận người rơm nhằm vào tài liệu ví dụ. HTMX chỉ phục vụ HTML, còn input thì dù bằng cách nào cũng phải được làm sạch.
      HTML có thể được render bằng bất kỳ ngôn ngữ template nào có xử lý việc đó.