3P by GN⁺ | ★ favorite | 댓글 1개
  • PostgreSQL 함수가 text/html을 직접 반환하면 PostgREST와 htmx만으로 AJAX 요청 결과를 받아 DOM 일부를 교체하는 to-do 앱을 만들 수 있음
  • 예제는 인증 없이 web_anonapi.todosapi.todos_id_seq 권한을 주고, Accept: text/html 요청을 처리하도록 커스텀 미디어 타입을 추가함
  • HTML 조립은 SQL 함수가 맡으며, api.sanitize_html, api.html_todo, api.html_all_todos가 입력값 이스케이프와 목록 렌더링을 처리함
  • 화면 동작은 hx-post, hx-get, hx-target, hx-trigger, hx-vals, hx-headers로 구성되며, 응답 HTML이 #todo-list-area나 개별 편집 영역을 대체함
  • 이 구성은 개념 증명에 가깝고, PostgREST 쪽에서는 HTML 처리를 개선하기 위해 plmustache 작업을 진행 중임

PostgREST와 htmx의 역할

  • PostgREST는 HTML 콘텐츠를 반환하고, htmx는 AJAX 요청 결과로 받은 HTML을 DOM 내부 요소 교체에 사용함
  • htmx는 HTML 응답을 기대하므로, 서버가 JSON 대신 HTML 조각을 돌려주는 흐름과 잘 맞음
  • 예제는 두 기술을 함께 사용할 때 가능한 구성을 보여주는 개념 증명
  • PostgREST는 HTML 처리 측면을 더 개선하기 위해 plmustache를 작업 중임

to-do 앱 준비 설정

  • 예제는 Tutorial 0 - Get it Running을 기반으로 한 to-do 앱임
  • 단순화를 위해 인증을 사용하지 않고, 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로 이 페이지를 열 수 있음

목록 렌더링과 to-do 추가

  • 기존 to-do 목록을 HTML로 만들기 위해 세 가지 SQL 함수가 사용됨
    • api.sanitize_html(text): &, ", >, <, ' 문자를 HTML 엔티티로 치환함
    • api.html_todo(api.todos): 단일 to-do 항목을 HTML 조각으로 포맷함
    • api.html_all_todos(): 전체 항목을 하나의 HTML 문자열로 합치고, 항목이 없으면 There is nothing else to do. 메시지를 반환함
  • api.html_todoapi.html_all_todos는 목록 템플릿을 만드는 내부용 함수이며, PostgREST 엔드포인트로 직접 사용하지 않음
  • api.add_todo(_task text)는 새 to-do를 api.todos에 삽입한 뒤 갱신된 전체 목록 HTML을 반환함
  • 갱신된 api.index()는 htmx와 입력 폼을 포함함
    • hx-headers='{"Accept": "text/html"}'를 상위 요소에 넣어 하위 htmx 요청이 HTML 응답을 요청하게 함
    • 이 헤더가 없으면 PostgREST가 요청을 HTML로 인식하지 못함
  • to-do 추가 폼은 htmx 속성으로 동작함
    • hx-post="/rpc/add_todo": _task 입력값을 /rpc/add_todo로 POST 요청함
    • hx-target="#todo-list-area": 응답 HTML을 to-do 목록 영역에 넣음
    • hx-trigger="submit": 폼 제출 시 요청을 보냄
    • hx-on="htmx:afterRequest: this.reset()": 요청 완료 뒤 폼을 비움
  • 새 함수와 변경 사항을 반영하려면 스키마 캐시를 새로고침해야 함
  • 이후 http://localhost:3000/rpc/index에서 목록 표시와 새 to-do 추가를 확인할 수 있음

편집, 삭제, 완료 상태 변경

  • api.html_todo는 각 항목에 완료 상태 변경, 편집, 삭제 UI를 포함하도록 확장됨
  • 완료 상태 변경은 <form>과 htmx 속성으로 처리함
    • hx-post="/rpc/change_todo_state": 해당 엔드포인트로 AJAX POST 요청을 보냄
    • hx-vals='{"_id": ..., "_done": ...}': 숨김 입력 대신 요청 파라미터를 추가함
    • hx-trigger="click": 항목 클릭으로 요청을 실행함
  • 편집 버튼은 개별 작업을 입력 필드로 바꿈
    • hx-get="/rpc/html_editable_task": 편집 가능한 HTML 입력을 반환하는 엔드포인트를 호출함
    • hx-target="#todo-edit-area-...": 전체 목록 대신 개별 작업 영역만 교체함
    • hx-vals는 GET 요청 파라미터를 추가하며, 테이블 컬럼을 나타낼 때는 eq. 연산자가 필요함
  • 삭제 버튼은 hx-post="/rpc/delete_todo"로 해당 to-do 삭제 요청을 보냄
  • api.html_editable_task(_id int)는 특정 to-do를 편집할 수 있는 입력 필드 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에 해당하는 to-do를 삭제함
  • 스키마 캐시를 새로고침한 뒤 http://localhost:3000/rpc/index에서 to-do 편집, 삭제, 완료 처리가 가능함

댓글과 토론

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%를 내라는 식임
  • 개념 증명으로는 멋지고 구현도 칭찬할 만하지만, 사소하지 않은 웹앱에서 유지보수하려면 악몽처럼 보임

    • 확실히 웹사이트나 가벼운 앱용에 가까움. 그래도 그 범위 안에서는 꽤 많이 활용할 수 있음
    • 지금 Sqitch로 유지보수를 쉽게 만들 수 있는지 실험 중임. 여전히 해킹처럼 느껴지고 실제 사용 가능성에는 의심이 남지만, 재미있고 여러 고급 Postgres 기능을 배우는 중임
      https://sqitch.org/
    • 솔직히 우리 메인 웹 애플리케이션에서 이걸 쓰면 100배 쉬워지지 않을 부분이 뭔지 궁금함
    • 지금은 그렇다고 봄. 이제 CI 과정의 일부로 HTMX 라우트를 데이터베이스 서버에 컴파일하고 배포하는 방법만 있으면 됨
  • 이런 기능이나 코딩 패턴이 새롭거나 현대적인 애플리케이션에서도 쓰이는지 궁금함
    CouchDB라는 JSON 문서 데이터베이스는 HTTP 기반 API를 제공했고, 자바스크립트 인터프리터 안에서 만들 수 있는 어떤 형식으로든 응답할 수 있는 목록/상세 메서드가 내장돼 있었음. 즉 클라이언트가 데이터베이스를 직접 호출해서 HTML이나 JSON을 받을 수 있었고 서버가 필요 없었음
    하지만 v1 이후에는 그 방향의 작업을 멈췄는데, 유지보수가 악몽이 되기 때문이었음. 예전 PHP나 ASP 파일 하나에 SQL 문과 HTML이 섞여 있던 좋은 시절을 기억하는 사람이 많을 텐데, 이건 별로 달라 보이지 않음

    • 새 세대가 예전에 타당한 이유로 버려졌던 것들을 다시 배우는 것 같음
      13년쯤 전 개인 프로젝트에서 CouchDB의 웹 관련 기능을 정말 좋아했지만, 팀이 다루기에는 꽤 불편했음
    • 현대적이라고 보긴 어려움. Oracle은 25년도 더 전에 이런 걸 갖고 있었음
  • 내가 작업해 본 유일한 PostgREST 앱은 끔찍했음. 왜냐하면 이런 “단순한” 프레임워크 대부분이 그렇듯, 요구사항이 복잡해지기 전까지만 단순하기 때문임
    원래 작성자들은 원하는 결과를 얻기 위해 데이터베이스에 저장 프로시저를 잔뜩 쓰게 됐고, 그게 확장성 문제로 이어졌음. 늘 그렇듯 해결책은 SQL로 돌아가는 것임

    • 그 팀이 작업에 맞지 않는 도구를 고른 것처럼 들림. 엔드포인트 대부분에 복잡한 백엔드 로직이 있다면 PostgREST를 쓰면 안 됨
      PostgREST는 CRUD 앱을 위해 만들어졌고, 내가 접하는 애플리케이션 다수는 여기에 해당함. 가끔 필요한 맞춤 백엔드 로직은 별도 서버나 엣지 함수로 띄워서 처리하면 됨
    • PostGREST를 많이 쓰지만, 강한 가드레일을 둠. 실제 API 계층은 항상 있어야 하고, 이건 기본 로딩을 편하게 하는 보조 수단으로만 쓰는 게 좋다고 봄
      이런 도구들이 다 그렇듯 현실 요구사항으로 가면 도구에 맞춰 적응하는 비용이, 대체하려던 것인 SQL + 어떤 언어와 프레임워크보다 더 나빠짐. 이미 PostGREST도 복잡해지고 있는데, 이런 추가 기능은 내게 매력을 떨어뜨림
    • 저장 프로시저도 결국 함수 형태의 SQL 아닌가?
      Rails가 등장해서 느린 서버 측 언어에 비즈니스 로직을 넣는 게 좋은 생각이라고 모두가 믿게 만들기 전에는, 다들 이런 식으로 앱을 만들었음
  • 정말 깔끔한 웹 개발 스택임. HTML과 데이터베이스만 있고, 백엔드와 프런트엔드가 필요 없음

    • 90년대 중반에 Ohio의 Compuserve를 방문한 적이 있음. 웹이 막 시작되던 시기였고, 거기서 만난 엔지니어 한 명이 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

  • 이 앱에 잊지 않으려고 작업을 하나 적고 싶음. 내 작업은 이것임:
    이 경우 task 컬럼이 어디에서도 정제되는 것 같지 않음

    • 안 좋았던 옛날에는 뷰가 데이터베이스와 직접 대화했기 때문에 XSS가 만연했음. 그다음 템플릿 엔진과 모델을 중간에 두자 문제가 해결된 것처럼 보였음
      그런데 이제 데이터베이스가 뷰라서 다시 XSS가 생긴 건가? 이건 막을 수 있는 문제임
    • 맞음. htmx는 그 스크립트를 기꺼이 실행함. 데모에도 같은 버그가 있었음: https://github.com/bigskysoftware/htmx/pull/1995/files
    • 예제 문서에 대한 허수아비 공격처럼 보임. HTMX는 HTML을 제공할 뿐이고, 입력은 어떤 식으로든 정제해야 함
      HTML은 그런 처리를 해주는 어떤 템플릿 언어로도 렌더링할 수 있음