Erlang/OTP web framework

Real-time web apps in plain Erlang

Write views as Erlang terms. Arizona renders them on the server, keeps each page live over a WebSocket, and sends only the values that changed.

Read the docs

Arizona is in 0.x: the core is tested and in use, but the API can still change between minor versions.

Example

A live counter, in full

No JavaScript to write. A click is an event on the server, and the reply patches the one number that changed.

-module(counter).
-include_lib("arizona/include/arizona_stateful.hrl").
-export([mount/1, render/1, handle_event/3]).

mount(_Bindings) ->
    {#{id => ~"counter", count => 0}, #{}}.

render(Bindings) ->
    ?html(
        {'div', [{id, ?get(id)}], [
            {span, [], [?get(count)]},
            {button, [{az_click, arizona_js:push_event(~"inc")}], [~"+"]}
        ]}
    ).

handle_event(~"inc", _Payload, #{count := Count} = Bindings) ->
    {Bindings#{count => Count + 1}, #{}, []}.
[{arizona, [
    {server, #{
        adapter => arizona_roadrunner_server,
        transport_opts => [{port, 8080}],
        routes => [
            %% The page, its WebSocket and the client runtime.
            {live, ~"/", counter, #{
                layouts => [{my_layout, render}]
            }},
            {ws, ~"/ws", #{}},
            {asset, ~"/assets", {priv_dir, arizona, "static/assets/js"}}
        ]
    }}
]}].
%% Render views to HTML files. A layout that loads the static client
%% (arizona-static.min.js) and calls start() keeps the browser-side
%% commands working, with no server: this site is built this way.
{Written, []} = arizona_static:generate(~"dist", [
    {home_page, ~"index.html"},
    {about_page, ~"about/index.html"}
], #{layouts => [{site_layout, render}]}).

This page

Interactive, with no server

This site is generated by arizona_static and served as plain files. Its controls are the same event attributes a live page uses; Arizona's static client runs them in the browser, with no WebSocket.

  • The theme switch sets a value the browser owns

    arizona_js:set_all(~"theme", ~"dark")
  • Each tab sets the one the panels and the guide read

    arizona_js:set_all(~"tab", ~"config")
  • Get started opens a modal dialog

    arizona_js:show_modal(~"#quick-start")
  • Every code block copies itself

    arizona_js:copy_to_clipboard(~"#code-view")
  • The guide floats in its own window, and still drives the page

    arizona_js:request_pip(~"guide")

Example guide

Pop it out: it follows the page, and drives it.

?get(count)
Reads a binding. Only this slot re-renders when it changes.
arizona_js:push_event(~"inc")
The click becomes an event on the server.
handle_event/3
Returns the new bindings; the reply patches one number.
adapter
The server package: Roadrunner or Cowboy.
{live, Path, View, Opts}
A page that stays live over its socket.
{ws, ~"/ws", #{}}
The WebSocket every live page connects to.
{asset, ...}
Serves the client runtime from Arizona's priv.
arizona_static:generate/3
Renders views to HTML files, no server.
arizona-static.min.js
Runs the page's commands in the browser.
start()
What this site calls instead of connect('/ws').
Theme

Features

Why Arizona

Live by default

The first load is plain HTML. After that each page stays live over a WebSocket, and PubSub pushes updates to every connected tab.

Minimal updates

Templates split into static and dynamic parts at compile time. Statics go once; an update sends only the slots whose data changed.

Plain Erlang templates

Views are {Tag, Attrs, Children} terms the compiler checks. Streams, client-owned state and SPA navigation are built in.

BEAM reliability

Every live page is a supervised process. Deploys drain sessions gracefully and clients reconnect on their own.

Beyond the browser

The same views and diff engine drive native Android and iOS clients, and render to a terminal over a TTY or SSH.

Static when you want it

Generate pages like this one from the same views, with browser-side commands and no server to run.

Servers

Runs on the server you already have

The HTTP and WebSocket server is an adapter package, so Arizona fits in beside what you run today.

Get started

  1. 1Add Arizona and a server adapter to rebar.config

    {deps, [
        arizona,
        arizona_roadrunner
    ]}.
  2. 2Route a view in config/sys.config

    [{arizona, [
        {server, #{
            adapter => arizona_roadrunner_server,
            transport_opts => [{port, 8080}],
            routes => [
                %% The page, its WebSocket and the client runtime.
                {live, ~"/", counter, #{
                    layouts => [{my_layout, render}]
                }},
                {ws, ~"/ws", #{}},
                {asset, ~"/assets", {priv_dir, arizona, "static/assets/js"}}
            ]
        }}
    ]}].
  3. 3Start a shell and open http://localhost:8080

    rebar3 shell