Tutorial: Construindo um App Web
O HarbourBuilder inclui um componente TWebServer integrado que permite servir paginas web e APIs JSON diretamente da sua aplicacao. Neste tutorial voce vai construir um app web de Tarefas simples com uma GUI desktop e uma interface via navegador rodando lado a lado.
Passo 1: Criar o Projeto
- Crie um novo projeto chamado
TodoWebAppvia Arquivo → Novo Projeto. - Abra
main.prgno Editor de Codigo. - Vamos construir um formulario desktop para gerenciar tarefas e um servidor web que expoe os mesmos dados.
Passo 2: Configurar o Servidor Web
Arraste um TWebServer da aba de paleta Internet para o formulario, ou crie no codigo. Defina a porta e defina suas rotas.
#include "hbbuilder.ch" static aTodos := {} // Lista de tarefas compartilhada function Main() local oForm, oServer, oBrw, oGet, oBtnAdd local cTask := "" // --- Config do servidor web --- DEFINE WEBSERVER oServer PORT 8080 oServer:Route( "GET", "/", { |oReq, oRes| ServeHomePage( oReq, oRes ) } ) oServer:Route( "GET", "/api/todos", { |oReq, oRes| GetTodosJSON( oReq, oRes ) } ) oServer:Route( "POST", "/api/todos", { |oReq, oRes| AddTodoJSON( oReq, oRes ) } ) oServer:Route( "DELETE", "/api/todos/:id", { |oReq, oRes| DeleteTodoJSON( oReq, oRes ) } ) oServer:Start() // --- UI Desktop --- DEFINE FORM oForm TITLE "App de Tarefas (Desktop + Web na porta 8080)" ; SIZE 600, 500 FONT "Segoe UI", 10 @ 10, 10 BROWSE oBrw ; OF oForm SIZE 560, 350 ; HEADERS { "#", "Tarefa", "Status" } ; WIDTHS { 40, 350, 100 } @ 380, 10 GET oGet VAR cTask OF oForm SIZE 400, 24 @ 380, 420 BUTTON oBtnAdd PROMPT "Adicionar" ; OF oForm SIZE 100, 32 ; ACTION ( AAdd( aTodos, { oGet:GetValue(), "pendente" } ), ; RefreshGrid( oBrw ), oGet:SetValue( "" ) ) RefreshGrid( oBrw ) oForm:OnClose := { || oServer:Stop(), .T. } ACTIVATE FORM oForm CENTERED return nil
O TWebServer roda em uma thread em segundo plano, entao seu formulario desktop permanece totalmente interativo.
Tanto a GUI quanto o navegador veem o mesmo array aTodos. Alteracoes de qualquer lado
sao imediatamente visiveis ao outro.
Passo 3: Servir uma Pagina HTML
A rota raiz / serve uma pagina HTML completa. Use oRes:SendHTML() para retornar conteudo HTML.
static function ServeHomePage( oReq, oRes ) local cHTML := '<!DOCTYPE html>' + ; '<html><head><title>App de Tarefas</title>' + ; '<style>body{font-family:sans-serif;max-width:600px;margin:2em auto}' + ; 'input{padding:8px;width:70%} button{padding:8px 16px}' + ; 'li{padding:4px 0}</style></head><body>' + ; '<h1>Lista de Tarefas</h1>' + ; '<input id="task" placeholder="Nova tarefa...">' + ; '<button onclick="addTask()">Adicionar</button>' + ; '<ul id="list"></ul>' + ; '<script>' + ; 'async function load(){let r=await fetch("/api/todos");' + ; 'let d=await r.json();let h="";' + ; 'd.forEach((t,i)=>h+="<li>"+t[0]+" ["+t[1]+"] " + ; '+"<a href=# onclick=del("+i+")>x</a></li>");' + ; 'document.getElementById("list").innerHTML=h}' + ; 'async function addTask(){let v=document.getElementById("task").value;' + ; 'await fetch("/api/todos",{method:"POST",' + ; 'headers:{"Content-Type":"application/json"},' + ; 'body:JSON.stringify({task:v})});load()}' + ; 'async function del(i){await fetch("/api/todos/"+i,' + ; '{method:"DELETE"});load()}' + ; 'load();</script></body></html>' oRes:SendHTML( cHTML ) return nil
Passo 4: Retornar JSON para a API
As rotas de API usam oRes:SendJSON() para retornar dados estruturados que o JavaScript do navegador
(ou qualquer cliente HTTP) pode consumir.
static function GetTodosJSON( oReq, oRes ) oRes:SendJSON( hb_jsonEncode( aTodos ) ) return nil static function AddTodoJSON( oReq, oRes ) local hBody := hb_jsonDecode( oReq:cBody ) AAdd( aTodos, { hBody[ "task" ], "pendente" } ) oRes:SendJSON( '{"status":"ok"}' ) return nil static function DeleteTodoJSON( oReq, oRes ) local nId := Val( oReq:Param( "id" ) ) + 1 if nId >= 1 .and. nId <= Len( aTodos ) ADel( aTodos, nId ) ASize( aTodos, Len( aTodos ) - 1 ) endif oRes:SendJSON( '{"status":"ok"}' ) return nil
Passo 5: Atualizar a Grade Desktop
static function RefreshGrid( oBrw ) local aData := {}, n for n := 1 to Len( aTodos ) AAdd( aData, { n, aTodos[ n ][ 1 ], aTodos[ n ][ 2 ] } ) next oBrw:SetArray( aData ) oBrw:Refresh() return nil
Passo 6: Compilar e Testar
- Pressione F9 para compilar e executar.
- O formulario desktop aparece com uma lista de tarefas vazia. Adicione algumas tarefas usando a GUI.
- Abra um navegador e navegue para
http://localhost:8080. - Voce ve as mesmas tarefas renderizadas como HTML. Adicione ou exclua tarefas do navegador.
- Volte para o desktop — os dados sao compartilhados em tempo real.
Visao Geral da Arquitetura
TForm + TBrowse"] --- C["Dados Compartilhados
Array aTodos"] B["TWebServer
Porta 8080"] --- C end D["Navegador
http://localhost:8080"] -->|"HTTP GET/POST/DELETE"| B A -->|"Acesso direto ao array"| C style A fill:#58a6ff,stroke:#388bfd,color:#0d1117 style B fill:#3fb950,stroke:#2ea043,color:#0d1117 style C fill:#d2a8ff,stroke:#bc8cff,color:#0d1117 style D fill:#f0883e,stroke:#d18616,color:#0d1117
Para aplicacoes web maiores, use oServer:Static( "/assets", "./www" ) para servir CSS, JavaScript,
e arquivos de imagem de uma pasta local em vez de incorporar HTML em strings.
Pronto para adicionar inteligencia ao seu app? Continue para o tutorial Integracao com IA para conectar a LLMs locais e executar inferencia diretamente da sua aplicacao.