PostgREST: fornecendo conteúdo HTML com htmx
(postgrest.org)- Quando uma função do PostgreSQL retorna
text/htmldiretamente, é possível criar um app de to-do que recebe o resultado de requisições AJAX e substitui partes do DOM usando apenas PostgREST e htmx - O exemplo concede permissões de
api.todoseapi.todos_id_seqaweb_anonsem autenticação e adiciona um tipo de mídia personalizado para processar requisições comAccept: text/html - A montagem do HTML fica a cargo de funções SQL, e
api.sanitize_html,api.html_todoeapi.html_all_todoscuidam do escape das entradas e da renderização da lista - O comportamento da interface é composto com
hx-post,hx-get,hx-target,hx-trigger,hx-valsehx-headers, e o HTML de resposta substitui#todo-list-areaou áreas individuais de edição - Essa configuração é mais próxima de uma prova de conceito, e o lado do PostgREST está trabalhando em
plmustachepara melhorar o processamento de HTML
Papel do PostgREST e do htmx
- O PostgREST retorna conteúdo HTML, e o htmx usa o HTML recebido como resultado de requisições AJAX para substituir elementos dentro do DOM
- Como o htmx espera respostas em HTML, ele combina bem com um fluxo em que o servidor devolve fragmentos HTML em vez de JSON
- O exemplo mostra uma configuração possível ao usar as duas tecnologias juntas como uma prova de conceito
- O PostgREST está desenvolvendo plmustache para melhorar ainda mais o tratamento de HTML
Configuração inicial do app de to-do
- O exemplo é um app de to-do baseado em Tutorial 0 - Get it Running
- Para simplificar, ele não usa autenticação e concede ao usuário
web_anonas permissões necessáriasgrant allna tabelaapi.todosgrant usage, selectna sequênciaapi.todos_id_seq
- Para que o PostgREST reconheça requisições do navegador com
Accept: text/htmlcomo HTML, é preciso adicionar um handler de tipo de mídia- criar o domínio
"text/html"sobretext - com essa configuração, o PostgREST pode retornar documentos HTML brutos
- criar o domínio
Retorno da página HTML básica
- A função
api.index()retorna"text/html"e gera o documento HTML básico - A página inclui o título
PostgREST + HTMX To-Do Listjunto com os seguintes recursos - No navegador, essa página pode ser aberta em
http://localhost:3000/rpc/index
Renderização da lista e adição de to-do
- Três funções SQL são usadas para gerar a lista de to-dos em HTML
api.sanitize_html(text): substitui os caracteres&,",>,<e'por entidades HTMLapi.html_todo(api.todos): formata um único item de to-do como fragmento HTMLapi.html_all_todos(): junta todos os itens em uma única string HTML e, se não houver itens, retorna a mensagemThere is nothing else to do.
api.html_todoeapi.html_all_todossão funções internas para montar o template da lista e não são usadas diretamente como endpoints do PostgRESTapi.add_todo(_task text)insere um novo to-do emapi.todose depois retorna o HTML da lista completa já atualizada- A versão atualizada de
api.index()inclui htmx e o formulário de entradahx-headers='{"Accept": "text/html"}'é colocado no elemento pai para que as requisições htmx filhas solicitem respostas em HTML- sem esse cabeçalho, o PostgREST não reconhece a requisição como HTML
- O formulário para adicionar to-dos funciona com atributos do htmx
hx-post="/rpc/add_todo": envia via POST o valor de entrada_taskpara/rpc/add_todohx-target="#todo-list-area": insere o HTML de resposta na área da lista de to-doshx-trigger="submit": envia a requisição ao submeter o formuláriohx-on="htmx:afterRequest: this.reset()": limpa o formulário após a conclusão da requisição
- É preciso atualizar o cache de esquema para refletir as novas funções e alterações
- Depois disso, é possível verificar a exibição da lista e a adição de novos to-dos em
http://localhost:3000/rpc/index
Edição, remoção e alteração do estado de conclusão
api.html_todoé expandida para incluir, em cada item, a interface de alteração de estado, edição e remoção- A alteração do estado de conclusão é tratada com
<form>e atributos do htmxhx-post="/rpc/change_todo_state": envia uma requisição AJAX POST para esse endpointhx-vals='{"_id": ..., "_done": ...}': adiciona parâmetros à requisição em vez de usar campos ocultoshx-trigger="click": executa a requisição ao clicar no item
- O botão de edição transforma a tarefa individual em um campo de entrada
hx-get="/rpc/html_editable_task": chama o endpoint que retorna um campo HTML editávelhx-target="#todo-edit-area-...": substitui apenas a área da tarefa individual, e não a lista inteirahx-valsadiciona parâmetros à requisição GET e, ao representar uma coluna de tabela, é necessário o operadoreq.
- O botão de remoção envia uma solicitação para excluir o to-do correspondente com
hx-post="/rpc/delete_todo" api.html_editable_task(_id int)retorna o HTML de um campo de entrada para editar um to-do específico- envia o nome da tarefa alterado com
hx-post="/rpc/change_todo_task" - atualiza em
hx-trigger="submit,focusout"ao submeter ou ao perder o foco - inclui
hx-headers='{"Accept": "text/html"}'
- envia o nome da tarefa alterado com
- Todos os endpoints que alteram dados retornam o HTML da lista completa após a modificação
api.change_todo_state(_id int, _done boolean): atualiza a colunadoneapi.change_todo_task(_id int, _task text): atualiza a colunataskapi.delete_todo(_id int): remove o to-do correspondente a_id
- Após atualizar o cache de esquema, é possível editar, remover e marcar to-dos como concluídos em
http://localhost:3000/rpc/index
1 comentários
Opiniões no Hacker News
PostgREST é um dos meus projetos open source favoritos. Acho que a Supabase ter se tornado uma empresa de US$ 1 bilhão se deve em grande parte ao excelente design do PostgREST e do Postgres.
Não sei como a Supabase patrocina esse projeto, mas espero que seja em uma escala bem grande. É triste ver a realidade do financiamento de open source quando um projeto usado como dependência central por pelo menos centenas de empresas que geram receita tem só 12 patrocinadores pagos.
https://www.patreon.com/postgrest/about
https://github.com/steve-chavez
Por isso, agora só uso AGPLv3 ou licenças parecidas. Algo como: se quiser usar comercialmente, pague 1% da receita bruta.
Como prova de conceito é legal, e a implementação merece elogios, mas para manter em uma web app que não seja trivial parece um pesadelo.
https://sqitch.org/
Fico me perguntando se esse tipo de recurso ou padrão de código é novo, ou se também é usado em aplicações modernas.
O CouchDB, um banco de dados de documentos JSON, oferecia uma API baseada em HTTP e tinha métodos embutidos de lista/detalhe que podiam responder em qualquer formato que fosse possível criar dentro de um interpretador JavaScript. Ou seja, o cliente podia chamar o banco de dados diretamente e receber HTML ou JSON, sem precisar de servidor.
Mas, depois da v1, eles pararam de seguir nessa direção porque a manutenção virava um pesadelo. Muita gente deve se lembrar dos bons tempos em que um único arquivo PHP ou ASP misturava instruções SQL e HTML; isto não parece muito diferente.
Uns 13 anos atrás, eu adorava os recursos web do CouchDB em um projeto pessoal, mas eles eram bem inconvenientes para uma equipe lidar.
O único app PostgREST em que trabalhei foi horrível. Porque, como a maioria desses frameworks “simples”, eles só são simples até os requisitos ficarem complexos.
Os autores originais acabaram usando um monte de stored procedures no banco de dados para conseguir os resultados desejados, e isso levou a problemas de escalabilidade. Como sempre, a solução é voltar para SQL.
O PostgREST foi criado para apps CRUD, e muitas das aplicações que encontro se encaixam nisso. A lógica de backend customizada que às vezes é necessária pode ser tratada em um servidor separado ou em edge functions.
Como acontece com todas essas ferramentas, quando se chega aos requisitos reais, o custo de se adaptar à ferramenta fica pior do que aquilo que ela tentava substituir: SQL + alguma linguagem e framework. O PostGREST já está ficando complexo, e esses recursos adicionais o tornam menos atraente para mim.
Antes de o Rails aparecer e fazer todo mundo acreditar que colocar lógica de negócio em uma linguagem lenta do lado do servidor era uma boa ideia, todos criavam apps desse jeito.
É uma stack de desenvolvimento web realmente limpa. Só HTML e banco de dados, sem precisar de backend nem frontend.
https://github.com/omnigres/omnigres
Quando o brilho do HTMX passar, o mantenedor de então acabará admitindo que tudo precisa ser reescrito do zero.
Usei PostgREST sem HTMX em um projeto antigo, e foi impressionante ver até onde dava para levar.
HTMX também parece combinar bem, mas não sei o quanto eu gostaria de manter templates HTMX dentro de funções SQL.
É bom para brincar, mas inadequado para construir algo confiável.
Do ponto de vista de Haskell, PostgREST é interessante. Porque parece um projeto óbvio demais.
Mas é justamente por isso que é genial. Gosto porque é uma ideia muito Haskell.
Que ferramentas adicionais você acha necessárias para transformar esse conceito em uma stack sustentável com boa experiência de usuário também em aplicações de médio e grande porte?
Eu também tentei criar algo parecido sobre SQL; a stack técnica era uma camada OpenAPI implementada com SQL em templates Jinja e YML, mas ainda assim acho que ficaria limitada ao escopo de ferramentas internas. Está aqui: https://jinj.at
Show HN relacionado: renderizar HTML a partir de SQL com pg_render
https://news.ycombinator.com/item?id=38677852
Quero anotar uma tarefa para não esquecer neste app. Meu trabalho é este:
Neste caso, a coluna task não parece ser sanitizada em lugar nenhum
Mas agora, como o banco de dados é a view, o XSS voltou? Isso é um problema que dá para evitar
O HTML pode ser renderizado com qualquer linguagem de templates que faça esse tratamento