Framework web

lxweb

Apps web que ficam no ar

Framework web para Lx inspirado no Phoenix. Declare rotas com scopes e pipelines, responda pelos controllers, renderize views em HTML e entregue interfaces em tempo real com LiveView sobre WebSockets — tudo rodando sobre Cowboy, na BEAM, com gettext (i18n) e hot-reload em dev embutidos.

v0.1.0 ~85% completo

Highlights

  • Roteador com scopes & pipelines
  • Controllers: JSON, text, render, redirect
  • LiveView sobre WebSockets
  • Path params (:id)
  • gettext (i18n)
  • Hot-reload em dev + cliente de teste E2E

Features

Roteamento

Agrupe rotas em scopes sob um prefixo de URL, cada um com seu pipeline de plugs. Path params como /posts/:id são extraídos automaticamente.

Pipelines

Plugs compostáveis por scope: fetch_params, fetch_session, put_secure_headers, accept_json, logger — encadeie o que precisar.

Controllers

Funções simples (conn, params) -> conn. Responda com json, text, render, redirect, send_resp, defina status e headers, ou faça halt do pipeline.

Views & templates

Módulos render/2 com a diretiva template (layouts, partials) e component tags. Mantenha a marcação fora da sua lógica.

LiveView

Componentes reativos: o servidor guarda o estado e envia patches de HTML sobre WebSockets. Bindings lx-click / lx-change / lx-submit; morphdom aplica os diffs no cliente.

Testes

lxweb_request simula requisições HTTP completas pelo seu roteador real — sem TCP. Asserte status, body e headers em testes simples.

Examples

Roteador: scopes & pipelines

require "lxweb_router"
require "lxweb_pipeline"

def dispatch(conn, method, path) do
  pipelines = %{
    browser: [
      {:lxweb_pipeline, :fetch_params, []},
      {:lxweb_pipeline, :put_secure_headers, []}
    ],
    api: [{:lxweb_pipeline, :accept_json, []}]
  }
  routes = [
    {:scope, "/api", :api, [
      {:get,  "/posts",      :post_controller, :list},
      {:post, "/posts",      :post_controller, :create}
    ]},
    {:scope, "/", :browser, [
      {:get,  "/",           :page_controller, :index},
      {:get,  "/posts/:id",  :post_controller, :show},
      {:live, "/counter",    :counter_live}
    ]}
  ]
  lxweb_router:dispatch(conn, method, path, pipelines, routes)
end

Controller: JSON, params, redirect

require "lxweb_controller"

def show(conn, params) do
  id = params[:id]
  lxweb_controller:json(conn, %{id: id, title: "Hello"})
end

def create(conn, params) do
  conn
    |> lxweb_controller:put_status(201)
    |> lxweb_controller:json(%{ok: true, title: params[:title]})
end

LiveView: contador sobre WebSockets

require "lxweb/lxweb_live"

def mount(_params, _session, socket) do
  {:ok, lxweb_live:assign(socket, %{count: 0})}
end

def handle_event("inc", _params, socket) do
  c = socket.assigns[:count]
  {:noreply, lxweb_live:assign(socket, %{count: c + 1})}
end

def render(assigns) do
  ~L"""
  <div data-lx-topic="lv:counter">
    <h2>Contagem: #{assigns.count}</h2>
    <button lx-click="inc">+1</button>
  </div>
  """
end

Teste E2E (sem TCP)

require "lxweb_request"
require "lxweb_conn"

test "GET /posts/:id" do
  conn = lxweb_conn:test(:my_router)
  r = lxweb_request:get(conn, "/posts/42")
  assert r.status == 200
  assert lxweb_request:response_header(r, "content-type") == "application/json"
end

Install

# project.yml
dependencies:
  lxweb:
    path: ../../lx_libs/lxweb
  cowboy: "~> 2.0"

# depois
$ lx deps get & lx compile & lx run