DevLog 20250613: Ol'ista Web Framework

DevLog 20250613: Ol'ista Web Framework

Backer posted Originally published at dev.to 4 min read

Overview

For web app development, we need a framework for Divooka. To approach this problem, we study existing frameworks in existing languages.

API Style Survey

If we look at any existing web server API, like ASP.Net Core below:

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);
var app     = builder.Build();

// Serve default files like index.html
app.UseDefaultFiles();
// Serve static files from wwwroot/
app.UseStaticFiles();

app.Run();

or Flask:

from flask import Flask

app = Flask(
    __name__,
    static_folder='static',      # default
    static_url_path=''           # serve at root instead of '/static'
)

@app.route('/')
def index():
    # Sends static/index.html
    return app.send_static_file('index.html')

if __name__ == '__main__':
    app.run(debug=True)

or NodeJS:

// server.js
import { createServer } from 'http';
import { createReadStream, statSync } from 'fs';
import { extname, join } from 'path';

const PUBLIC_DIR = join(process.cwd(), 'public');

createServer((req, res) => {
  // Map URL → file path
  const urlPath = req.url === '/' ? '/index.html' : req.url;
  const filePath = join(PUBLIC_DIR, urlPath);

  try {
    const stats = statSync(filePath);
    if (stats.isFile()) {
      const stream = createReadStream(filePath);
      // Minimal mime-type handling:
      const ext = extname(filePath).slice(1);
      res.setHeader('Content-Type', {
        html: 'text/html',
        js:   'application/javascript',
        css:  'text/css',
        png:  'image/png',
        jpg:  'image/jpeg',
      }[ext] || 'application/octet-stream');
      stream.pipe(res);
      return;
    }
  } catch {
    // fall through to 404
  }

  res.statusCode = 404;
  res.end('Not found');
}).listen(3000, () => console.log('Listening on http://localhost:3000'));

With Express.js:

// app.js
import express from 'express';
const app = express();

// Serve index.html automatically at '/'
app.use(express.static('public'));

// (Optional) Serve another folder at /assets
app.use('/assets', express.static('PublicAssets'));

app.listen(3000, () => {
  console.log('Static server running on http://localhost:3000');
});

There are some common features:

  1. We define what the server does
  2. We provide some "endpoints"
  3. And we let the server run wild

There is an obvious data-driven pattern. Now, if you have followed Divooka's development, or have seen a few use cases of the Dataflow context (e.g. with plotting), it would immediately appear to you: wow, we should be able to do this trivially in Divooka!

Indeed it is!

Ol'ista - The Divooka Way

Ol'ista (codename Oista) is Divooka Explore's builtin web framework.

A web server is configured using capabilities, those include: static file hosting, FTP server, web API, etc.

Serve Static Files

Serve Static Files

Create FTP Server

Create FTP Server

Serve Web API

Serve Web API

Standard Components

For most common things that almost all web frameworks provide, Ol'ista provides it through the Static File Capability and Web API Capability. Configuration is done through succession of compositional APIs:

Compositional Configuration

You can serve many capabilities at once:

Serve multiple capabilities

Dynamic Behaviors

Dynamic behaviors or what's typically understood as web APIs are served through Web API Capability, and Divooka enables it through function references:

Server Side

Server Side

Full Setup (Server and Client)

Server & Client Setup

Result on Client

Request Result

Dynamic Webpages

Most web frameworks offers some kind of template language for easier generated HTML contents.

ASP .NET Core (Razor)

Views/Shared/_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"] – MyApp</title>
</head>
<body>
    <header>
        @await Html.PartialAsync("_Nav")
    </header>

    <main role="main">
        @RenderBody()
    </main>

    <footer>
        @RenderSection("Scripts", required: false)
    </footer>
</body>
</html>

Views/Home/Index.cshtml

@{
    ViewData["Title"] = "Home";
    var items = new[] { "Apple", "Banana", "Cherry" };
}
<h1>Hello, @User.Identity.Name!</h1>

<ul>
@foreach (var item in items)
{
    <li>@item</li>
}
</ul>

@section Scripts {
    <script>console.log("Page loaded");</script>
}

Python Flask (Jinja2)

templates/base.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block title %}MyApp{% endblock %}</title>
</head>
<body>
    <header>
      {% include "nav.html" %}
    </header>

    <main>
      {% block content %}{% endblock %}
    </main>

    <footer>
      {% block scripts %}{% endblock %}
    </footer>
</body>
</html>

templates/index.html

{% extends "base.html" %}

{% block title %}Home – MyApp{% endblock %}

{% block content %}
  <h1>Hello, {{ current_user.name }}!</h1>
  <ul>
    {% for fruit in ["Apple","Banana","Cherry"] %}
      <li>{{ fruit }}</li>
    {% endfor %}
  </ul>
{% endblock %}

{% block scripts %}
  <script>console.log("Page loaded");</script>
{% endblock %}

Ruby on Rails (ERB)

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title><%= content_for?(:title) ? yield(:title) : "MyApp" %></title>
</head>
<body>
  <header>
    <%= render "nav" %>
  </header>

  <main>
    <%= yield %>
  </main>

  <footer>
    <%= yield :scripts if content_for?(:scripts) %>
  </footer>
</body>
</html>

app/views/home/index.html.erb

<% content_for :title, "Home – MyApp" %>

<h1>Hello, <%= current_user.name %>!</h1>

<ul>
  <% ["Apple","Banana","Cherry"].each do |fruit| %>
    <li><%= fruit %></li>
  <% end %>
</ul>

<% content_for :scripts do %>
  <script>console.log("Page loaded");</script>
<% end %>

Among those, ASP.Net Core and Ruby on Rails appear cleaner.

Comparison

Feature ASP .NET Core (Razor) Python Flask (Jinja2) Ruby on Rails (ERB)
File extension .cshtml .html (or .jinja2) .html.erb
Expression delimiter @Model.Property or @(...) {{ variable }} <%= @variable %>
Statement delimiter @{ ... }, @if(...) { ... } {% ... %} <% ... %>
Partial/templates include @await Html.PartialAsync("_Nav") {% include "nav.html" %} <%= render "nav" %>

For Divooka, as a start, I plan to implement something I call Markit - a simple string replacement. This awaits further work and will be updated on Ol'ista's wiki.

References

If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

DevLog 20250706: Speech to Text Transcription using OpenAI Whisper

Methodox - Jul 6

REST API - A Refresher

Methodox - Jun 2

AquaScript | Free JSON APIs

Hanzla Baig Dev - May 23

How to Add V2 on Swagger: Adding Version 2 (V2) with Swagger!

Madhu Hari - Jun 18

Understanding Equality in C# with IEquatable, Not Equals and ==

Spyros - Feb 6
chevron_left