Routing
Group routes into scopes under a URL prefix, each through its own pipeline of plugs. Path params like /posts/:id are extracted automatically.
Web apps that stay up
Phoenix-inspired web framework for Lx. Declare routes with scopes and pipelines, respond from controllers, render HTML views and ship real-time interfaces with LiveView over WebSockets — all running on Cowboy, on the BEAM, with gettext i18n and dev hot-reload built in.
Group routes into scopes under a URL prefix, each through its own pipeline of plugs. Path params like /posts/:id are extracted automatically.
Composable plugs per scope: fetch_params, fetch_session, put_secure_headers, accept_json, logger — chain what you need.
Plain functions (conn, params) -> conn. Respond with json, text, render, redirect, send_resp, set status and headers, or halt the pipeline.
render/2 modules with the template directive (layouts, partials) and component tags. Keep markup out of your logic.
Reactive components: the server holds state and pushes HTML patches over WebSockets. Bindings lx-click / lx-change / lx-submit; morphdom applies diffs on the client.
lxweb_request simulates full HTTP requests through your real router — no TCP. Assert status, body and headers in plain tests.
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
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
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>Count: #{assigns.count}</h2>
<button lx-click="inc">+1</button>
</div>
"""
end
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
# project.yml
dependencies:
lxweb:
path: ../../lx_libs/lxweb
cowboy: "~> 2.0"
# then
$ lx deps get & lx compile & lx run