DEV Community

Cover image for DevLog 20250613: Ol'ista Web Framework

DevLog 20250613: Ol'ista Web Framework

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();
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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'));
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 %}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)