Skip to content

Blog

Interactive components for backend-driven apps.

Components are one of the best ideas popularized by frontend frameworks.

A component gives a name and a boundary to a piece of UI. It can receive properties, contain other components and hide its implementation behind a small interface. Once you get used to thinking this way, going back to large templates made of loosely related includes and macros can feel limiting.

Yet components are still mostly treated as a frontend concept.

In traditional backend applications, reusable HTML is usually passive. A template partial or macro can render a form, a table or a dialog, but once that piece of UI becomes interactive, its implementation often spreads across several places: markup in a template, request handling in a Python route, and JavaScript responsible for sending the request or updating the page.

Frontend frameworks solve this by moving the component, its state and its behavior into the browser. This creates a coherent development model, but it often means building a frontend application alongside the backend application.

Hyperflask explores another option: treating components as backend abstractions.

A backend component can receive properties, contain children and compose other components. It can also run Python code and expose HTTP endpoints.

When called through HTMX, the component handles the request and returns the HTML representing its new state. HTMX then replaces the relevant part of the page.

The component becomes both a reusable unit of rendering and an addressable unit of interaction. It does not need to maintain a second representation of the application state in the browser. It accepts a request, calls regular Python code and returns HTML.

Supporting this model required more than a few helpers around Flask. Hyperflask introduced two related projects to make backend components feel natural.

The first is jinja-super-macros, which adds a component-oriented syntax to Jinja.

Instead of calling a macro like a function and relying on conventions for passing blocks of content, a component can be invoked with syntax such as <{Card}>...</{Card}>. Properties are declared alongside the component name, while nested content becomes its children.

The result looks closer to HTML and to the component syntax used by frontend frameworks, while remaining based on Jinja macros.

The second is jinjapy, a new file format created by the Hyperflask project to support this vision. A JinjaPy file combines Python frontmatter with a Jinja template, allowing the code responsible for preparing or handling a component to live next to its markup.

Putting Python and HTML in the same file may initially look like a violation of separation of concerns. But separation of concerns does not necessarily require separation by file type.

The endpoint that opens a dialog is closely related to the template for that dialog. When its fields change, both usually change. When the component disappears, both disappear. Co-locating them makes that relationship explicit.

That does not mean moving business logic into templates. Domain logic, authorization rules and reusable operations still belong in ordinary Python modules, models and services. The component handles the web-facing part of the feature: reading the request, calling the relevant application code and deciding which HTML to return.

This approach also changes the boundary between the backend and the browser.

SPAs commonly use JSON APIs because the frontend and backend are independent applications. In a backend-driven application, that separation is not always necessary. When the browser ultimately needs HTML, the server can return the updated representation directly.

The browser reports an interaction, the server processes it, and HTMX places the resulting HTML in the page. There is less need to reproduce rendering decisions and application state on both sides.

Backend-driven does not mean that every interaction must go through the server. Dropdowns, tabs and other local states are usually better handled in the browser. Hyperflask components can include Alpine.js, Web Components or framework-based components when they are useful.

This is partly inspired by Astro’s component islands: different parts of a page can use different levels of client-side interactivity rather than forcing the entire application into a single frontend architecture.

Hyperflask applies a similar idea to backend-driven applications. One component may be entirely server-rendered. Another may use HTMX for server interactions. A third may contain local JavaScript, while a particularly rich feature may be implemented as a frontend island.

The choice can be made at the level of the component rather than for the whole application.

Backend-driven apps will not suit every use cases. Applications with substantial offline behavior, complex local state or highly interactive editing tools may need more responsibility in the browser.

But for applications built largely around forms, tables, filters and server-side workflows, they offer an interesting way to combine component-based design with a Python-centered architecture.

Jinja-super-macros provides the composition model, JinjaPy brings Python and markup together, and HTMX connects components to the browser through regular HTTP and HTML.

Hyperflask is an attempt to see how far that combination can go.

Blending SQL and Python with SQLORM

At $dayjob, I use SQLAlchemy as an ORM. It's an amazing project, powerful and flexible. However, I've always felt some of the design choices didn't fit how I like to use an ORM. Notably:

  • I'm not a big fan of the Unit of Work pattern and the fact that you do not control when DML queries are issued. I like queries to be executed as soon as they are called in code.
  • I do not want my objects to be "attached" to a session or a specific database. I want to be able to fetch from one database and insert into another using the same object.
  • I mostly want plain objects to map a table row.
  • I want to write SQL by hand for complex queries. I do not want to use a query builder or DSL, I prefer writing actual SQL.
  • I do not care about abstracting the database. I usually choose a database server when starting a project and optimize for it.
  • Stay as close to DB-API as possible.

With these ideas in mind, SQLORM was born. (The name isn't great, I'm bad at finding names for projects like this.) It's inspired by many ORMs while bringing some unique features.

300 stars on Github

Almost a month ago, I announced Hyperflask and posted the link on Hacker News. The submission was very well recieved, staying 24h on the front page. Since then, the Hyperflask project on Github has reached 300 stars.

It will take time to build momentum around a new framework but I'm already very happy with the positive feedback I've recieved. Thank you to everybody who read about and/or tried Hyperflask !

If you have tried out the framework, please leave a comment on the Github disccusion thread or shout me an email (maxime.bouroumeau@gmail.com). I would love to hear about your experience.

Launch annoncement

After 2 years of working on & off on Hyperflask, I'm happy to finally announce a working version that can be used to create actual production apps ! Although it is still in beta, all the desired features are present and working.

In reality, Hyperflask is more than 10 years in the making ! My previous attempt at a comprehensive framework went nowhere but some of its ideas survived. The rise of htmx and the revival of backend driven apps led me to get excited again.

Backend-driven apps using something like htmx are not an answer to all type of web development. However, I'm convinced that they provide a solution for 80% of use cases (if not more) while simplying greatly the development experience, the cognitive load and the many footguns that frontend-heavy frameworks bring.

I love Flask as a framework but unfortunately, a lot of Flask extensions are barely maintained. In the meantime, other frameworks have introduced amazing innovations that greatly simplify the developer experience. Hyperflask is my attempt at synthetizing all the parts I like across various technologies and frameworks in a coherent stack. Integration between each chosen technology should be completely seamless.

Among these innovations, notable ones are:

More than just a framework, Hyperflask is a truly full stack solution. From dev environments, to UI framework to deployments.
On top of the previously mentionned features, Hyperflask provides:

All the technologies used by Hyperflask have been carefully selected for their quality, developer experience, ease of use, feature set and popularity.

The framework is optimized to limit "magic" features while ensuring ease of use. React Server Components or Phoenix LiveView are impressive but they hide too many things, often leading to many footguns and hacks around them. In Hyperflask, everything is just Python, standard Flask and Jinja with jinjapy files and htmx as the binding agent.

One of my side goals with Hyperflask is to revive and modernize the Flask ecosystem. As a result, almost all of the features listed before have been developed as independent Flask extensions. Hyperflask itself is mostly glue code to make them work together. Pick and choose the extensions you want for your own projects.

In the same spirit, there are some projects developed as part of Hyperflask that can be used indepently of Flask:

  • sqlorm, a performant and straighforward ORM that does not attempt to hide SQL
  • jinjapy, a new file format that combines python code and a jinja template
  • jinja-super-macros, a new syntax to call macros in jinja
  • uilib-spec, a specification to generate bindings for UI component libraries (see uilib-spec-daisyui for a use case)

All these projects are available on the Hyperflask Github organization.

Check out the Getting Started tutorial, read more about the goals, technologies and reasoning behind the project or deep dive into the guides.

PS: I've already used Hyperflask to build SQLify as part of my job at Digicoop / Kantree and the experience was very satisfying.