DEV Community

Cover image for Create a Phoenix LiveView Like App in JS with AppRun
yysun
yysun

Posted on • Edited on

Create a Phoenix LiveView Like App in JS with AppRun

Introduction

When I was creating the AppRun Websockets Template, I thought I was the only crazy person to push and run the 1 + 1 calculation on the server-side until I saw this video.

Phoenix LiveViewis a server-side solution using the Elixir programming language.

My AppRun WebSockets solution shares the same idea with Phoenix LiveView.

However, the AppRun solution is 100% JavaScript. For those JavaScript/TypeScript developers that are not ready to learn another programming language, the AppRun solution is for you.

This post is a step by step instruction of how to create a WebSockets based AppRun application.

Create the Project

To get started, run the following commands in the terminal or command shell.

npx degit apprunjs/apprun-websockets my-app
cd my-app
npm install
npm start
Enter fullscreen mode Exit fullscreen mode

You will see a single page application using Bootstrap.

Project Structure

The project has a typical express JS project structure.

Project Structure


Figure 1. Project Structure

  • server directory has the server-side code
  • server/index.js is the webserver
  • src directory has the client-side code
  • public directory has the static assets
  • public/index.html is the default web page of the app

The project also has a few npm scripts.

  • npm start:client: compiles and watches the client-side app
  • npm start:server: starts the webserver
  • npm start: starts client and server
  • npm run build: build the client-side app for production

Understand the Architecture

Client Side

On the Home page of the SPA, there are two counters. One runs locally in the browser. One runs on the server.

import { app, Component } from 'apprun';

export default class HomeComponent extends Component {
  state = 0;
  view = state => {
    const add = (state, num) => state + num;
    return <div>
      <h1>{state}</h1>
      <div>
        <button $onclick={[add, -1]}>-1</button>
        <button $onclick={[add, +1]}>+1</button>
        <div>Run the counter locally</div>
      </div>
      <hr />
      <div>
        <button $onclick={['//ws:', '@add', state, -1]}>-1</button>
        <button $onclick={['//ws:', '@add', state, +1]}>+1</button>
        <div>Run the counter on server through web sockets</div>
      </div>
    </div>
  };
  update = {
    '@add': (_, state) => state
  }
}
Enter fullscreen mode Exit fullscreen mode

They all use the AppRun event-driven programming model to trigger the state update and render lifecycle. The local counter uses local event directive. The server counter uses a global event //ws:, which means the send it to sever. The event parameters are @add, 1 or @add -1.

The main.tsx has five lines of code to send the event to the server through the web socket. The event name is @add. The event parameter is 1 or -1.

const ws = new WebSocket(`ws://${location.host}`);
app.on('//ws:', (event, state, ...args) => {
  const msg = { event, state, args };
  ws.send(JSON.stringify(msg));
});
Enter fullscreen mode Exit fullscreen mode

Server Side

On the server-side, index.js creates an express server and listen to the web socket communication. When it receives messages, it publishes the messages using AppRun.

const apprun = require('apprun').app;
require('./add');
const express = require('express');
const { createServer } = require('http');
const app = express();
const server = createServer(app);
const wss = new WebSocket.Server({ server });
wss.on('connection', function(ws) {
  ws.on('message', function (data) {
    try {
      const json = JSON.parse(data);
      apprun.run(json.event, json);
      ws.send(JSON.stringify(json));
    } catch (e) {
      console.error(e);
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Yes. AppRun runs on the server too to provide server-side event-driven programming.

The add.js is a server-side module that handles the @add event. It does the calculation to update the state. Then, in index.js, it sends results back to the web socket as an event using the same event name @add.

const app = require('apprun').app;
app.on('@add', function (data) {
  data.state += data.args[0];
});
Enter fullscreen mode Exit fullscreen mode

Back to Client

The main.tsx receives the event from the web socket. It just needs to publish as it is. The event is a global event that has the name @add. The home page then handles the @add event to get the state calculated on the server.

ws.onmessage = function (msg) {
  const {event, state} = JSON.parse(msg.data);
  app.run(event, state);
}
Enter fullscreen mode Exit fullscreen mode

We can summarize the process in the diagram below.

Events


Figure 2. Events Between Client and Server

Next Steps

You can use the AppRun—WebSockets template as the start point. The template has all you need to create your Phoenix LiveView like applications.

Or you can copy about 20 lines of JS code from the main.tsx and index.js into your existing application to start leveraging the power of WebSockets. E.g., database-driven applications using WebSockets.

Live Demo

Conclusion

Because we use JavaScript, our applications are full-featured SPA. We can have Bootstrap, D3, ChartJS and, even JQuery plug-ins.

The AppRun WebSockets solution is so simple that it does not even worth being an npm package. Use the AppRun—WebSockets template or copy about 20 lines of code into your codebase, you will have Phoenix LiveView like applications.

One last thing to mention is that Phoenix LiveView does the server-side rendering (SSR), which is also trivial using AppRun. However, I favor the Progress Web App (PWA) over SSR because PWA provides offline, home screen icon, and many other features. There is also an AppRun template for PWA.

Top comments (2)

Collapse
 
jkleiser profile image
Jon Kleiser

When I click the two lower buttons (counter on server) in the above Live Demo, I see no reaction. Is something wrong here?
A possible typo: Under "Next Steps" I read "The template has you have all". Is that what's intended? (Could be.)

Collapse
 
yysun profile image
yysun

The live demo runs on glitch. Glitch kills the server when it is idle. Refresh will wake it up.