HTML over WebSockets: real-time SPAs with barely any JavaScript

andros1 pts0 comments

HTML over WebSockets: real-time SPAs with barely any JavaScript | Andros Fenollosa

Skip to content

Building a SPA (Single-page Application) is a complex puzzle: a JavaScript framework that draws the view, an API serving JSON, and 2 independent codebases forced to understand each other through contracts. It is an accepted, professionalized scenario. But being a standard does not make it the only way. I want to show you another approach, one that is not new but has gained traction over the years: HTML over WebSockets .

The idea is this: instead of sending JSON and assembling the HTML in the browser, the server sends the HTML already built and the client just places it where it belongs. All the rendering logic stays in the Back-End, in a single language, with no need for contracts or an API. This pattern is known as hypermedia or HTML over the wire . What matters about how the HTML travels is that it determines the latency and the bidirectionality of the communication. There are three variants:

Over HTTP , request by request, like htmx or Unicorn.

Over SSE , opening a one-way, continuous channel from server to client, like Datastar.

Over WebSockets , a permanent, bidirectional channel, like Phoenix LiveView or Django LiveView.

The channel is so important that it determines the application's architecture and its communication pattern.

In this article I am going to talk about HTML over WebSockets : the real-time and bidirectional variant of the family. The one that lets you build a SPA with barely any JavaScript, in a single language, with no contracts and a single rendering engine. We will see what it is, how it works and when it pays off compared to its HTTP or SSE cousins.

Origin

Chris McCord , creator of Phoenix (the most popular framework in the Elixir ecosystem), presented at ElixirConf 2019 a technology called LiveView. In just 15 minutes he built a Twitter clone that worked in real time without adding any rendering JavaScript or a popular framework (React, Angular, Vue...) to manage the View, proving that you could stay in the Back-End and be productive with a sweet hint of good performance. Since then the solution has grown popular, inspiring other developers to build HTML-over-WebSockets implementations in other languages . You can go back to the Back-End without giving up the good parts of the Front-End.

How does it work?

Even though it might not seem so at first, JavaScript is used on the client. Its job is not to render but to create a communication channel with WebSockets and place the received HTML in the right spot. Plus other secondary tasks like animations, event handling, etc.

McCord's solution is not to send the Front-End a JSON, but HTML that needs no preprocessing. That way we move the rendering load, and all its logic, to the Back-End. OK but... how do we get the server to send us new content immediately and without making a request? Easy: with WebSockets.

Let's review the traditional system from the introduction. From the web I make an HTTP request, the browser starts the action and gets a JSON with all the raw information in response. The next step is to interpret it and build the corresponding HTML.

sequenceDiagram<br>participant C as Browser<br>participant S as Server<br>C->>S: 1. HTTP request (GET /api/article/2/) and maybe auth<br>S->>S: 2. Query the DB<br>S->>S: 3. Build a JSON with the article data<br>S-->>C: 4. Return JSON<br>C->>C: 5. Parse the JSON<br>C->>C: 6. Build the HTML with its rendering engine<br>With HTML over WebSockets , that same request travels over a permanent channel and the response is already assembled HTML, with no JSON in between. And since the channel never closes, the server can even get ahead and send changes without the client asking.

The flow with WebSockets is now the following, ignoring the initial connection and authentication, which happen only once when the channel opens:

sequenceDiagram<br>participant C as Browser<br>participant S as Server (Back-End)<br>C->>S: 1. Sends a text: "I want /article/2/"<br>S->>S: 2. Query the DB<br>S->>S: 3. Render HTML with its template engine<br>S-->>C: 4. Return the assembled HTML/CSS/JS<br>"...<br>C->>C: 5. Place the HTML where it belongs<br>Simple, elegant and fast. The client takes care of placing the HTML where it belongs and listening for events. The server handles the rest. You do not have to worry about client state or rendering logic, since everything lives in the Back-End.

The full, complex cycle, including opening the connection and authentication, would look like this:

sequenceDiagram<br>participant C as Browser<br>participant S as Server (Back-End)<br>C->>S: 1. Opens WebSocket connection and authenticates<br>Note over C,S: A single persistent channel<br>C->>S: 2. Sends a text: "I want /article/2/"<br>S->>S: 3. Query the DB<br>S->>S: 4. Render HTML with its template engine<br>S-->>C: 5. Return the assembled HTML/CSS/JS<br>"...<br>C->>C: 6. Place the HTML where it belongs<br>Note over S,C: The server can also push<br>changes without the client asking (broadcast)<br>On top of...

html websockets server json back channel

Related Articles