3 คะแนน โดย GN⁺ 2023-12-19 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • หากฟังก์ชัน PostgreSQL ส่งคืน text/html ได้โดยตรง ก็สามารถสร้างแอป to-do ที่รับผลลัพธ์จากคำขอ AJAX และแทนที่บางส่วนของ DOM ได้ด้วยแค่ PostgREST และ htmx
  • ตัวอย่างนี้ให้สิทธิ์ web_anon กับ api.todos และ api.todos_id_seq โดยไม่ใช้การยืนยันตัวตน และเพิ่ม custom media type เพื่อรองรับคำขอ Accept: text/html
  • การประกอบ HTML เป็นหน้าที่ของฟังก์ชัน SQL โดย api.sanitize_html, api.html_todo, api.html_all_todos จะจัดการการ escape ค่าที่ป้อนเข้าและการเรนเดอร์รายการ
  • การทำงานบนหน้าจอประกอบด้วย hx-post, hx-get, hx-target, hx-trigger, hx-vals, hx-headers และ HTML ที่ตอบกลับจะเข้ามาแทนที่ #todo-list-area หรือพื้นที่แก้ไขแต่ละรายการ
  • โครงสร้างนี้ใกล้เคียงกับ proof of concept และฝั่ง PostgREST ก็กำลังพัฒนา plmustache เพื่อปรับปรุงการจัดการ HTML

บทบาทของ PostgREST และ htmx

  • PostgREST ส่งคืน คอนเทนต์ HTML และ htmx ใช้ HTML ที่ได้จากผลลัพธ์ของคำขอ AJAX เพื่อแทนที่องค์ประกอบภายใน DOM
  • htmx คาดหวังการตอบกลับเป็น HTML จึงเข้ากันได้ดีกับรูปแบบที่เซิร์ฟเวอร์ส่งคืน HTML fragment แทน JSON
  • ตัวอย่างนี้เป็น proof of concept ที่แสดงให้เห็นว่าการใช้สองเทคโนโลยีนี้ร่วมกันสามารถทำอะไรได้บ้าง
  • PostgREST กำลังพัฒนา plmustache เพื่อปรับปรุงด้านการจัดการ HTML ให้ดียิ่งขึ้น

การเตรียมตั้งค่าแอป to-do

  • ตัวอย่างนี้เป็นแอป to-do ที่อ้างอิงจาก Tutorial 0 - Get it Running
  • เพื่อความเรียบง่าย ตัวอย่างนี้ไม่ใช้การยืนยันตัวตน และมอบสิทธิ์ที่จำเป็นให้กับผู้ใช้ web_anon
    • grant all บนตาราง api.todos
    • grant usage, select บนซีเควนซ์ api.todos_id_seq
  • หากต้องการให้ PostgREST มองคำขอ Accept: text/html จากเบราว์เซอร์เป็น HTML ต้องเพิ่ม media type handler
    • สร้างโดเมน "text/html" จาก text
    • การตั้งค่านี้ทำให้ PostgREST ส่งคืนเอกสาร HTML แบบดิบได้

การส่งคืนหน้า HTML พื้นฐาน

  • ฟังก์ชัน api.index() ส่งคืน "text/html" และสร้างเอกสาร HTML พื้นฐาน
  • ภายในหน้ามีชื่อ PostgREST + HTMX To-Do List พร้อมทรัพยากรต่อไปนี้
    • Pico CSS สำหรับจัดสไตล์
    • Ionicons สำหรับแสดงไอคอน
  • ในเบราว์เซอร์สามารถเปิดหน้านี้ได้ที่ http://localhost:3000/rpc/index

การเรนเดอร์รายการและการเพิ่ม to-do

  • ใช้ฟังก์ชัน SQL สามตัวเพื่อสร้างรายการ to-do เดิมให้ออกมาเป็น HTML
    • api.sanitize_html(text): แทนที่อักขระ &, ", >, <, ' เป็น HTML entity
    • api.html_todo(api.todos): จัดรูปแบบรายการ to-do เดี่ยวเป็น HTML fragment
    • api.html_all_todos(): รวมทุกรายการเป็นสตริง HTML เดียว และหากไม่มีรายการจะส่งข้อความ There is nothing else to do.
  • api.html_todo และ api.html_all_todos เป็นฟังก์ชันภายในสำหรับสร้างเทมเพลตรายการ จึงไม่ได้ใช้เป็น PostgREST endpoint โดยตรง
  • api.add_todo(_task text) แทรก to-do ใหม่ลงใน api.todos แล้วส่งคืน HTML ของรายการทั้งหมดที่อัปเดตแล้ว
  • api.index() ที่อัปเดตแล้วจะมี htmx และฟอร์มรับข้อมูลรวมอยู่ด้วย
    • ใส่ hx-headers='{"Accept": "text/html"}' ไว้ที่องค์ประกอบระดับบน เพื่อให้คำขอ htmx ที่อยู่ด้านล่างร้องขอการตอบกลับแบบ HTML
    • หากไม่มี header นี้ PostgREST จะไม่มองคำขอเป็น HTML
  • ฟอร์มเพิ่ม to-do ทำงานผ่านแอตทริบิวต์ของ htmx
    • hx-post="/rpc/add_todo": ส่งค่าอินพุต _task แบบ POST ไปยัง /rpc/add_todo
    • hx-target="#todo-list-area": นำ HTML ที่ตอบกลับไปใส่ในพื้นที่รายการ to-do
    • hx-trigger="submit": ส่งคำขอเมื่อมีการ submit ฟอร์ม
    • hx-on="htmx:afterRequest: this.reset()": ล้างฟอร์มหลังคำขอเสร็จสิ้น
  • ต้องรีเฟรช schema cache เพื่อให้ฟังก์ชันใหม่และการเปลี่ยนแปลงต่าง ๆ มีผล
  • หลังจากนั้นสามารถดูรายการและเพิ่ม to-do ใหม่ได้ที่ http://localhost:3000/rpc/index

การแก้ไข การลบ และการเปลี่ยนสถานะเสร็จสิ้น

  • api.html_todo ถูกขยายให้มี UI สำหรับเปลี่ยนสถานะเสร็จสิ้น แก้ไข และลบในแต่ละรายการ
  • การเปลี่ยนสถานะเสร็จสิ้นจัดการด้วย <form> และแอตทริบิวต์ของ htmx
    • hx-post="/rpc/change_todo_state": ส่งคำขอ AJAX แบบ POST ไปยัง endpoint นั้น
    • hx-vals='{"_id": ..., "_done": ...}': เพิ่มพารามิเตอร์ของคำขอแทนการใช้ hidden input
    • hx-trigger="click": เรียกคำขอเมื่อคลิกรายการ
  • ปุ่มแก้ไขจะเปลี่ยนงานแต่ละรายการให้เป็นช่องอินพุต
    • hx-get="/rpc/html_editable_task": เรียก endpoint ที่ส่งคืนอินพุต HTML สำหรับแก้ไขได้
    • hx-target="#todo-edit-area-...": แทนที่เฉพาะพื้นที่ของงานรายการนั้น ไม่ใช่ทั้งรายการทั้งหมด
    • hx-vals ใช้เพิ่มพารามิเตอร์ให้คำขอ GET และเมื่ออ้างถึงคอลัมน์ของตารางจะต้องใช้ตัวดำเนินการ eq.
  • ปุ่มลบจะส่งคำขอลบ to-do นั้นด้วย hx-post="/rpc/delete_todo"
  • api.html_editable_task(_id int) ส่งคืน HTML ของช่องอินพุตที่ใช้แก้ไข to-do ที่ระบุ
    • ส่งชื่องานที่แก้ไขแล้วด้วย hx-post="/rpc/change_todo_task"
    • อัปเดตเมื่อ submit หรือเมื่อโฟกัสหลุดด้วย hx-trigger="submit,focusout"
    • รวม hx-headers='{"Accept": "text/html"}' ไว้ด้วย
  • endpoint ที่ใช้เปลี่ยนข้อมูลทั้งหมดจะส่งคืน HTML ของรายการทั้งหมดหลังแก้ไขเสร็จ
    • api.change_todo_state(_id int, _done boolean): อัปเดตคอลัมน์ done
    • api.change_todo_task(_id int, _task text): อัปเดตคอลัมน์ task
    • api.delete_todo(_id int): ลบ to-do ที่ตรงกับ _id
  • หลังรีเฟรช schema cache แล้ว จะสามารถแก้ไข ลบ และทำเครื่องหมายเสร็จสิ้นให้ to-do ได้ที่ http://localhost:3000/rpc/index

1 ความคิดเห็น

 
GN⁺ 2023-12-19
ความคิดเห็นจาก Hacker News
  • PostgREST เป็นหนึ่งในโปรเจกต์โอเพนซอร์สที่ฉันชอบที่สุด มองว่าที่ Supabase กลายเป็นบริษัทระดับพันล้านดอลลาร์ได้ ส่วนใหญ่ก็เพราะการออกแบบที่ยอดเยี่ยมของ PostgREST และ Postgres
    ไม่รู้ว่า Supabase สนับสนุนโปรเจกต์นี้อย่างไรบ้าง แต่หวังว่าน่าจะเป็นในระดับใหญ่มาก น่าเศร้ากับความเป็นจริงของการสนับสนุนทางการเงินในโอเพนซอร์ส เมื่อเห็นว่าโปรเจกต์ที่มีบริษัททำรายได้หลายร้อยแห่งใช้เป็น dependency หลัก กลับมีผู้สนับสนุนแบบเสียเงินเพียง 12 คน
    https://www.patreon.com/postgrest/about

    • มันเป็นส่วนแกนหลักของสแตก Supabase เลย ดังที่กล่าวไว้ พวกเขาจ้าง Steve เมนเทนเนอร์หลักให้โฟกัสทำงานกับ PostgREST เป็นหลัก
      https://github.com/steve-chavez
    • ตอนนี้เป็น 13 คนแล้ว แต่ก็ยังแค่ $1,529/เดือน อยู่ดี
    • ไม่เคยรู้มาก่อนว่า PostgREST เป็น ส่วนหนึ่งของ Supabase เคยนึกว่า Supabase เป็นของลอกเลียนแบบที่สร้างขึ้นใหม่ตั้งแต่ต้น
    • ได้ยินคำพูดซ้ำซากที่มักโผล่มาในสถานการณ์แบบนี้ลอยมาแล้ว เช่น “ผู้ใช้ไม่ได้มีหน้าที่ต้องสนับสนุนคุณทางการเงิน” หรือ “ถ้าไม่ชอบก็ไม่ควรปล่อยภายใต้ไลเซนส์แบบ permissive”
      เพราะแบบนี้ตอนนี้เลยใช้แต่ AGPLv3 หรือไลเซนส์คล้ายกัน ถ้าจะใช้เชิงพาณิชย์ก็จ่าย 1% ของรายได้รวมไปเลย
  • ในฐานะ proof of concept มันเท่มาก และการทำออกมาก็น่าชื่นชม แต่ถ้าต้องดูแลรักษาในเว็บแอประดับที่ไม่ใช่งานเล็ก ๆ ก็ดูเหมือน ฝันร้าย

    • เห็นได้ชัดว่ามันเหมาะกับเว็บไซต์หรือ แอปเบา ๆ มากกว่า ถึงอย่างนั้นภายในขอบเขตนั้นก็ยังเอาไปใช้ได้เยอะพอสมควร
    • ตอนนี้กำลังลองดูว่า Sqitch จะช่วยให้การดูแลรักษาง่ายขึ้นได้ไหม ยังรู้สึกเหมือนเป็นการแฮ็กอยู่ และก็ยังสงสัยว่าจะใช้จริงได้แค่ไหน แต่ก็สนุกดีและได้เรียนรู้ฟีเจอร์ขั้นสูงของ Postgres หลายอย่าง
      https://sqitch.org/
    • พูดตรง ๆ คือสงสัยว่าถ้าเอาสิ่งนี้ไปใช้กับเว็บแอปหลักของเรา จะมีส่วนไหนบ้างที่มัน ไม่ง่ายขึ้น 100 เท่า
    • ตอนนี้ก็มองว่าใช่ แค่ต้องมีวิธีคอมไพล์และ deploy เส้นทาง HTMX ไปยังเซิร์ฟเวอร์ฐานข้อมูลเป็นส่วนหนึ่งของ กระบวนการ CI เท่านั้น
  • สงสัยว่าความสามารถหรือแพตเทิร์นการเขียนโค้ดแบบนี้ยังถือว่าใหม่ หรือยังถูกใช้ในแอปพลิเคชันสมัยใหม่หรือไม่
    CouchDB ซึ่งเป็นฐานข้อมูลเอกสาร JSON เคยมี API แบบ HTTP และมีเมธอด list/detail ในตัวที่สามารถตอบกลับได้ในแทบทุกรูปแบบที่สร้างได้ภายใน JavaScript interpreter นั่นหมายความว่าไคลเอนต์สามารถเรียกฐานข้อมูลโดยตรงเพื่อรับ HTML หรือ JSON ได้ และไม่ต้องมีเซิร์ฟเวอร์เลย
    แต่หลังจาก v1 พวกเขาก็หยุดไปในทิศทางนั้น เพราะการดูแลรักษามันกลายเป็นฝันร้าย ใครที่จำยุคทองของ PHP หรือไฟล์ ASP แบบเก่าที่มีทั้ง SQL และ HTML ปนกันอยู่ในไฟล์เดียวได้ ก็คงจะรู้ว่านี่แทบไม่ต่างกันเลย

    • เหมือนคนรุ่นใหม่กำลังกลับไปเรียนรู้สิ่งที่เคยถูกทิ้งไปแล้วด้วยเหตุผลที่สมควร
      ราว 13 ปีก่อนฉันชอบฟีเจอร์ด้านเว็บของ CouchDB มากในโปรเจกต์ส่วนตัว แต่สำหรับการทำงานเป็นทีมมันค่อนข้างใช้งานลำบาก
    • จะเรียกว่าสมัยใหม่ก็คงไม่ค่อยได้ Oracle มีอะไรแบบนี้มาตั้งแต่กว่า 25 ปีก่อนแล้ว
  • แอป PostgREST เพียงตัวเดียวที่ฉันเคยทำด้วยนั้นแย่มาก เพราะเหมือนเฟรมเวิร์ก “เรียบง่าย” แบบนี้ส่วนใหญ่ มันจะเรียบง่ายอยู่แค่จนกว่าความต้องการจะเริ่มซับซ้อน
    คนเขียนเดิมสุดท้ายต้องใช้ stored procedure ในฐานข้อมูลจำนวนมากเพื่อให้ได้ผลลัพธ์ที่ต้องการ และนั่นก็นำไปสู่ปัญหาด้าน scalability ตามมา อย่างที่มักเป็นกัน ทางออกก็คือกลับไปใช้ SQL

    • ฟังดูเหมือนทีมนั้นเลือกเครื่องมือไม่เหมาะกับงาน ถ้า endpoint ส่วนใหญ่มีแบ็กเอนด์ลอจิกซับซ้อน ก็ไม่ควรใช้ PostgREST
      PostgREST ถูกสร้างมาสำหรับแอปแบบ CRUD และแอปจำนวนมากที่ฉันเจอก็เข้าข่ายนี้ ลอจิกแบ็กเอนด์เฉพาะทางที่จำเป็นเป็นครั้งคราวก็แยกไปทำในเซิร์ฟเวอร์อีกตัวหรือ edge function ได้
    • ใช้ PostGREST เยอะเหมือนกัน แต่ตั้ง guardrail ไว้เข้มพอสมควร มองว่าควรมีชั้น API จริงอยู่เสมอ และให้สิ่งนี้เป็นแค่ตัวช่วยสำหรับงานโหลดพื้นฐานให้สะดวกขึ้นเท่านั้น
      เครื่องมือแนวนี้ก็เหมือนกันหมด พอเจอความต้องการจริงในโลกความเป็นจริง ต้นทุนในการปรับตัวให้เข้ากับเครื่องมือกลับแย่กว่าสิ่งที่มันตั้งใจจะมาแทนที่อย่าง SQL + ภาษาและเฟรมเวิร์กสักตัวเสียอีก ทุกวันนี้ PostGREST เองก็เริ่มซับซ้อนขึ้นแล้ว และฟีเจอร์เพิ่มแบบนี้ยิ่งทำให้ฉันรู้สึกดึงดูดน้อยลง
    • stored procedure เองก็เป็นแค่ SQL ในรูปแบบฟังก์ชัน ไม่ใช่หรือ?
      ก่อนที่ Rails จะมาและทำให้ทุกคนเชื่อว่าการใส่ business logic ไว้ในภาษาเซิร์ฟเวอร์ที่ช้านั้นเป็นความคิดที่ดี ผู้คนก็สร้างแอปกันแบบนี้ทั้งนั้น
  • เป็นสแตกพัฒนาเว็บที่สะอาดมาก มีแค่ HTML กับฐานข้อมูล ไม่ต้องมีทั้งแบ็กเอนด์และฟรอนต์เอนด์

    • ช่วงกลางยุค 90 ฉันเคยไปเยือน Compuserve ที่ Ohio ตอนนั้นเว็บเพิ่งเริ่มต้น และวิศวกรคนหนึ่งที่เจอที่นั่นได้โชว์ร้านขายเพลงที่สร้างโดยให้ SQL stored procedure ส่งคืน HTML มาเลย :)
    • ถ้ารู้สึกว่านี่เท่ ลองดู Omnigres ด้วยก็น่าสนใจ
      https://github.com/omnigres/omnigres
    • เราเคยเดินเส้นทางนี้มาแล้ว ตอนแรกมันดูสะอาดดี แต่ไม่ดีต่อ การดูแลระยะยาว การซัพพอร์ต และการฝึกคน
      เมื่อความใหม่ของ HTMX จางลง ผู้ดูแลในตอนนั้นสุดท้ายก็จะยอมรับว่าต้องเขียนใหม่ทั้งหมดตั้งแต่ต้น
    • แบ็กเอนด์กับฟรอนต์เอนด์ยังคงมีอยู่ แค่มันถูก พันกันอยู่ในโค้ดฐานข้อมูล โดยตรงทั้งหมดเท่านั้น
    • สุดท้ายก็คือการกลับไปหา เมนเฟรม อีกครั้ง คราวนี้แค่มีเบราว์เซอร์ทำหน้าที่เป็นเทอร์มินัล
  • เคยใช้ PostgREST ในโปรเจกต์เก่าโดยไม่มี HTMX และก็ประทับใจว่ามันไปได้ไกลแค่ไหน
    HTMX เองก็ดูเข้ากันดี แต่ไม่แน่ใจนักว่าอยากดูแลรักษาเทมเพลต HTMX มากแค่ไหนเมื่อมันอยู่ใน SQL function

    • ไม่สนใจเลย ฉันเคยผ่านประสบการณ์นั้นมากับ PHP แล้ว แพตเทิร์นแบบนี้ขาดฟีเจอร์สำคัญอย่าง static analysis, การทดสอบบนเครื่อง, การรีแฟกเตอร์ และการเปลี่ยนแปลงขนาดใหญ่ที่ส่งมอบได้แบบอะตอมมิก
      เอาไว้ทำเล่น ๆ ได้ดี แต่ไม่เหมาะกับการสร้างของที่ต้องเสถียร
  • ถ้ามองจากมุม Haskell แล้ว PostgREST น่าสนใจมาก เพราะมันดูเป็นโปรเจกต์ที่ชัดเจนเสียจนเหมือนไม่ต้องคิดอะไร
    แต่นั่นแหละที่ทำให้มันอัจฉริยะ เป็นไอเดียที่ Haskell มาก ๆ ซึ่งฉันชอบ

  • หากจะทำให้แนวคิดนี้กลายเป็น สแตกที่ดูแลรักษาได้ พร้อมประสบการณ์ผู้ใช้ที่ดีแม้ในแอปพลิเคชันขนาดกลางถึงใหญ่ คุณคิดว่าจำเป็นต้องมีเครื่องมืออะไรเพิ่มเติมบ้าง?

    • สำหรับการเสิร์ฟเว็บไซต์แบบสแตติก ใช้ Astro(https://astro.build) และใช้ PostgREST Htmx กับคอมโพเนนต์แบบเรียบง่ายที่ขับเคลื่อนด้วยข้อมูล
    • ถ้าเป็นขนาดกลางถึงใหญ่ คิดว่าน่าจะต้องมี ชั้น abstraction แยกต่างหาก หรือก็คือ API
      ผมเองก็เคยพยายามทำอะไรคล้ายกันบน SQL โดยใช้สแตกเทคโนโลยีเป็นเทมเพลต Jinja, SQL และชั้น OpenAPI ที่เขียนด้วย YML แต่ถึงอย่างนั้นก็คงจำกัดไว้ในขอบเขตของเครื่องมือภายในอยู่ดี ดูได้ที่: https://jinj.at
  • Show HN ที่เกี่ยวข้อง: เรนเดอร์ HTML จาก SQL ด้วย pg_render
    https://news.ycombinator.com/item?id=38677852

  • อยากจดงานหนึ่งไว้ในแอปนี้กันลืม งานของผมคือสิ่งนี้:
    ในกรณีนี้ดูเหมือนว่า คอลัมน์ task จะไม่ได้ถูก sanitize จากที่ไหนเลย

    • ในยุคเก่าอันเลวร้าย วิวคุยกับฐานข้อมูลโดยตรง จึงมี XSS ระบาดไปทั่ว จากนั้นเมื่อเอาเทมเพลตเอนจินกับโมเดลมาไว้ตรงกลาง ปัญหาก็ดูเหมือนจะถูกแก้ไป
      แต่ตอนนี้ฐานข้อมูลกลายเป็นวิวเอง งั้น XSS ก็กลับมาอีกแล้วหรือ? นี่เป็นปัญหาที่ป้องกันได้
    • ใช่ htmx ยินดีรันสคริปต์นั้นเลย เดโมก็มีบั๊กแบบเดียวกัน: https://github.com/bigskysoftware/htmx/pull/1995/files
    • ฟังดูเหมือนเป็นการ โจมตีหุ่นไล่กา ต่อเอกสารตัวอย่างมากกว่า HTMX แค่ส่ง HTML มาเท่านั้น และอินพุตก็ควรถูก sanitize ไม่ทางใดก็ทางหนึ่ง
      HTML สามารถเรนเดอร์ด้วยภาษาเทมเพลตใด ๆ ที่จัดการเรื่องนี้ให้ได้