DEV Community

Cover image for New Server-Driven UI in Elixir: Stateless, RESTful, and HTML-Native with WFC
Elanat Framework
Elanat Framework

Posted on

New Server-Driven UI in Elixir: Stateless, RESTful, and HTML-Native with WFC

What if a web application could provide rich, interactive user interfaces while keeping the server stateless, using standard HTTP, working naturally with RESTful architectures, and remaining based on HTML?

What if the server could define UI behavior without maintaining a server-side representation of the browser DOM?

This is the approach introduced by WebForms Core (WFC).

WebForms Core is a server-side UI manipulation technology developed by Elanat. It allows server-side code to generate commands that describe operations on the HTML DOM. These commands are then executed by WebFormsJS, the browser runtime of WFC.

The basic architecture is:

Server
   ↓
WebForms
   ↓
Commands
   ↓
WebFormsJS
   ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

With Elixir and Phoenix, this becomes:

Phoenix Controller
        ↓
    WebForms
        ↓
    Commands
        ↓
   WebFormsJS
        ↓
    HTML DOM
Enter fullscreen mode Exit fullscreen mode

WebForms Core in Elixir

The important point is that the server does not need to keep a copy of the browser's UI state.

The browser owns the DOM.

The server generates commands.

WebFormsJS executes them.


A Stateless Approach to Server-Driven UI

Many interactive web architectures introduce some form of persistent UI state on the server.

WFC takes a different approach.

The server can process a request, generate the required UI commands, return the response, and finish processing the request.

Conceptually:

Request
   ↓
Server
   ↓
Generate Commands
   ↓
Response
   ↓
Server Request State Ends
Enter fullscreen mode Exit fullscreen mode

The server does not need to maintain a continuously synchronized representation of the client's DOM.

This makes the architecture naturally compatible with stateless HTTP applications and horizontal scaling.

For example, multiple server instances can process independent requests:

                 ┌── Server A
Browser ── HTTP ─┼── Server B
                 └── Server C
Enter fullscreen mode Exit fullscreen mode

The application does not need a particular server instance to continuously own the browser's UI state.


HTML Is Still the Interface

WFC does not require a proprietary UI markup language.

The interface is HTML.

A normal Phoenix template can contain:

<form method="post" action="/">
    <label for="txt_Name">Your Name</label>
    <input name="txt_Name" id="txt_Name" type="text">

    <input name="btn_SetBodyValue"
           type="submit"
           value="Click to send data">
</form>
Enter fullscreen mode Exit fullscreen mode

There is no requirement to convert the page into a separate component language.

The HTML remains the HTML that the browser understands.

WebFormsJS operates on that existing DOM.

This creates a simple relationship:

HTML
  +
WebFormsJS
  +
Server Commands
  =
Interactive UI
Enter fullscreen mode Exit fullscreen mode

RESTful and HTTP-Friendly

A Server-Driven UI architecture does not necessarily require a persistent connection between the server and the browser.

WFC commands can be returned through ordinary HTTP responses.

For example:

POST /submit
        ↓
Phoenix Controller
        ↓
    WebForms
        ↓
WebForms Core Response
        ↓
  HTTP Response
        ↓
   WebFormsJS
        ↓
   DOM Update
Enter fullscreen mode Exit fullscreen mode

This fits naturally into the request/response model of the web.

At the same time, WFC is not restricted to HTTP request/response communication.

Depending on the application's requirements, server-generated commands can also be transported through technologies such as:

  • HTTP
  • WebSocket
  • Server-Sent Events (SSE)

The command architecture remains the same.

Only the transport mechanism changes.

                  ┌── HTTP
WebForms Commands ├── WebSocket
                  └── SSE
                       ↓
                  WebFormsJS
                       ↓
                   HTML DOM
Enter fullscreen mode Exit fullscreen mode

This separation between command generation and transport allows the UI architecture to remain independent from a single communication mechanism.


Server-Orchestrated UI

WFC can be described as a Server-Orchestrated UI architecture.

The server determines which UI operations should occur.

For example, server-side Elixir code can express:

form =
  WebForms.new()
  |> WebForms.set_font_size(InputPlace.tag("form"), 20)
  |> WebForms.set_background_color(InputPlace.tag("form"), "#eeeeee")
  |> WebForms.set_disabled(InputPlace.name("btn_SetBodyValue"), true)
Enter fullscreen mode Exit fullscreen mode

The server is defining behavior.

The browser is executing behavior.

WebFormsJS receives the generated commands and applies them to the DOM.

The server does not need to send a complete replacement page.

It can send only the operations required for the current interaction.


The Commander–Executor Architecture

This creates a clear division between the server and the browser.

             SERVER
                │
                ▼
          WebForms Class
            Commander
                │
                ▼
       WebForms Commands
                │
                ▼
             BROWSER
                │
                ▼
           WebFormsJS
            Executor
                │
                ▼
            HTML DOM
Enter fullscreen mode Exit fullscreen mode

The WebForms class is the Commander.

It creates the instructions.

WebFormsJS is the Executor.

It interprets and executes the instructions.

This is different from sending application-level JavaScript from the server.

The server generates WebForms Core commands, while WebFormsJS provides the runtime that understands those commands.


Building a Phoenix Application

Let's see how this architecture works in a real Elixir application.

The example uses Phoenix and the wfc package.

Add WFC to the project's dependencies:

defp deps do
  [
    {:wfc, "~> 2.1"}
  ]
end
Enter fullscreen mode Exit fullscreen mode

Then fetch the dependency:

mix deps.get
Enter fullscreen mode Exit fullscreen mode

The package provides the WebFormsCore namespace:

alias WebFormsCore.{WebForms, InputPlace}
Enter fullscreen mode Exit fullscreen mode

The Phoenix View

Create an index.html.heex view:

<script type="module" src="/script/web-forms.js"></script>

<form method="post" action="/">
    <label for="txt_Name">Your Name</label>
    <input name="txt_Name" id="txt_Name" type="text">
    <br>

    <label for="txt_FontSize">Set Font Size</label>
    <input name="txt_FontSize"
           id="txt_FontSize"
           type="number"
           value="16"
           min="10"
           max="36">
    <br>

    <label for="txt_BackgroundColor">Set Background Color</label>
    <input name="txt_BackgroundColor"
           id="txt_BackgroundColor"
           type="text">
    <br>

    <input name="btn_SetBodyValue"
           type="submit"
           value="Click to send data">
</form>
Enter fullscreen mode Exit fullscreen mode

The first GET / request renders the complete HTML page.

The browser now owns the interface.

When the user submits the form, Phoenix receives the form data.

The controller can then generate commands instead of rendering another complete page.


The Phoenix Controller

Create my_controller.ex:

defmodule MyAppWeb.MyController do
  use MyAppWeb, :controller

  alias WebFormsCore.{WebForms, InputPlace}

  def index(conn, _params) do
    render(conn, :index)
  end

  def submit(conn, %{
        "txt_Name" => name,
        "txt_BackgroundColor" => background_color,
        "txt_FontSize" => font_size,
        "btn_SetBodyValue" => _button
      }) do
    form =
      WebForms.new()
      |> WebForms.set_font_size(InputPlace.tag("form"), String.to_integer(font_size))
      |> WebForms.set_background_color(InputPlace.tag("form"), background_color)
      |> WebForms.set_disabled(InputPlace.name("btn_SetBodyValue"), true)
      |> WebForms.add_tag(InputPlace.tag("form"), "h3", nil)
      |> WebForms.set_text(InputPlace.tag("h3"), "Welcome #{name}!")

    text(conn, WebForms.response(form))
  end
end
Enter fullscreen mode Exit fullscreen mode

The controller creates a WebForms object and builds the UI operations.

For example:

WebForms.set_font_size(
  InputPlace.tag("form"),
  String.to_integer(font_size)
)
Enter fullscreen mode Exit fullscreen mode

changes the font size of the <form> element.

This:

WebForms.set_background_color(
  InputPlace.tag("form"),
  background_color
)
Enter fullscreen mode Exit fullscreen mode

changes its background color.

And:

WebForms.set_disabled(
  InputPlace.name("btn_SetBodyValue"),
  true
)
Enter fullscreen mode Exit fullscreen mode

disables the submit button.

The controller can also create new HTML elements:

WebForms.add_tag(
  InputPlace.tag("form"),
  "h3",
  nil
)
Enter fullscreen mode Exit fullscreen mode

and set their contents:

WebForms.set_text(
  InputPlace.tag("h3"),
  "Welcome #{name}!"
)
Enter fullscreen mode Exit fullscreen mode

Finally:

text(conn, WebForms.response(form))
Enter fullscreen mode Exit fullscreen mode

returns the generated WebForms Core response.

The response contains the commands that WebFormsJS will execute.


Phoenix Router

Add the routes:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  scope "/", MyAppWeb do
    get "/", MyController, :index
    post "/", MyController, :submit
  end
end
Enter fullscreen mode Exit fullscreen mode

The request flow is now:

GET /
  ↓
Phoenix
  ↓
HTML View
  ↓
Browser
Enter fullscreen mode Exit fullscreen mode

After submitting the form:

POST /
  ↓
Phoenix Controller
  ↓
WebForms
  ↓
WebForms Core Commands
  ↓
HTTP Response
  ↓
WebFormsJS
  ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

No server-side DOM needs to be maintained.


WebFormsJS

WebFormsJS is the browser runtime responsible for executing WebForms Core commands.

Load it with:

<script type="module" src="/script/web-forms.js"></script>
Enter fullscreen mode Exit fullscreen mode

Its role is not to replace HTML.

Its role is to operate on HTML.

The relationship is:

Server
  ↓
Command
  ↓
WebFormsJS
  ↓
Existing HTML DOM
Enter fullscreen mode Exit fullscreen mode

This makes WebFormsJS an execution layer rather than a replacement for the browser's native document model.

Get WebFormsJS in GitHub: https://github.com/webforms-core/Web_forms

Get WebFormsJS in Elanat: https://elanat.net/webforms-js


Designed for Scale

Statelessness is particularly important for large web applications.

When UI state does not need to remain attached to a particular server process, requests can be distributed across multiple application instances.

                         ┌── Phoenix Server 1
                         │
Browser ── Load Balancer ├── Phoenix Server 2
                         │
                         └── Phoenix Server 3
Enter fullscreen mode Exit fullscreen mode

Each request can be processed independently.

The server receives the request, executes application logic, generates the required UI commands, returns the response, and completes the request.

This architecture can work naturally with common scaling mechanisms such as load balancing and multiple application instances.

The browser remains responsible for the DOM.

The server remains responsible for application logic and command generation.


Stateless Does Not Mean Less Interactive

A stateless server does not mean a static interface.

WFC commands can perform a wide range of DOM operations.

The server can instruct the browser to:

  • Change text
  • Change attributes
  • Change styles
  • Add elements
  • Remove elements
  • Enable or disable controls
  • Replace content
  • Move through UI flows
  • Respond to user interactions

The interaction can therefore remain dynamic while the server remains stateless.

This is an important distinction:

Stateless Server
        ≠
Static UI
Enter fullscreen mode Exit fullscreen mode

A server can be stateless while still orchestrating rich browser interactions.


One UI Architecture, Multiple Transports

Another important property of WFC is the separation between what should happen and how the command is transported.

The UI command itself can remain conceptually the same:

Set Text
Set Style
Add Element
Disable Button
Replace Content
Enter fullscreen mode Exit fullscreen mode

The transport can vary:

HTTP
WebSocket
SSE
Enter fullscreen mode Exit fullscreen mode

This makes it possible to select a communication model based on application requirements without changing the fundamental UI command architecture.

For a conventional form submission, HTTP may be sufficient.

For continuous server-to-browser events, SSE may be appropriate.

For bidirectional real-time communication, WebSocket may be appropriate.

The WebFormsJS runtime remains the execution environment.


HTML-Native Server-Driven UI

The web platform already provides a powerful document model: HTML and the DOM.

WFC builds its UI architecture around that model.

There is no requirement for a separate virtual DOM.

There is no requirement for a separate frontend project.

There is no requirement for a client-side component build pipeline.

The fundamental UI remains:

HTML → DOM
Enter fullscreen mode Exit fullscreen mode

WebForms Core adds a server-generated command layer:

Server
   ↓
Commands
   ↓
WebFormsJS
   ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

This keeps the browser interface close to the native web platform while allowing the server to orchestrate interactive behavior.


A Different Way to Build Interactive Web Applications

WebForms Core combines several architectural ideas:

Stateless Server
       +
RESTful / HTTP Architecture
       +
HTML-Native UI
       +
Server-Generated Commands
       +
Browser Runtime
       +
Multiple Transports
       ↓
Server-Orchestrated UI
Enter fullscreen mode Exit fullscreen mode

For Elixir applications, Phoenix provides the server-side web framework, while WFC provides the server-side UI command layer and WebFormsJS provides the browser-side execution runtime.

The result is a model where:

The server owns the application logic.

The browser owns the DOM.

WebForms defines the UI commands.

WebFormsJS executes the commands.

And the server does not need to maintain a continuously synchronized copy of the browser interface.


Final Architecture

The complete architecture can be summarized in one diagram:

                         SERVER
                            │
                     Phoenix / Elixir
                            │
                            ▼
                      WebForms Class
                        Commander
                            │
                            ▼
                  WebForms Core Commands
                            │
                 ┌──────────┼──────────┐
                 │          │          │
                HTTP       SSE     WebSocket
                 │          │          │
                 └──────────┼──────────┘
                            │
                            ▼
                        WebFormsJS
                         Executor
                            │
                            ▼
                        HTML DOM
Enter fullscreen mode Exit fullscreen mode

This is the core idea behind the new Server-Driven UI approach of WebForms Core:

The server orchestrates.

The browser executes.

HTML remains the interface.

The server remains stateless.

WebForms Core is a technology owned and developed by Elanat.

Official website: https://elanat.net

WebForms Core: https://elanat.net/webforms-core

WebForms Core GitHub: https://github.com/webforms-core

Top comments (0)