Bottle + HTMX vs Streamlit🔗
NOTE: This isn't a debate that real people have.
So why compare them...?
It's a comparison of workflows for quickly building small, interactive web apps.
streamlit is the new hotness in Data Science and Machine Learning.
It basically turns Python scripts into websites, pre-styled and pre-packaged with a good looking component library.
But its install size is not small!
The Python web framework
htmx are each dependency free and each is contained in a single
.js file (js can be loaded from cdn).
bottle is ~4500 lines of Python and
Combining the two allows for writing interactive apps with just Python and HTML.
But first some context for people who haven't heard of
Python Full Stack Development🔗
Before I found
streamlit I played with different Python web frameworks (
They all try to make spinning up a web server easier (some with a heavier hand than others cough django cough).
They all have some templating mechanism (ex.
jinja2) for sending html to the frontend.
But many modern "full-stack" tutorials won't bother with this feature.
The flavor of the moment I've seen is JSON Rest Api + React/Vue/Angular Frontend.
So as an aspiring developer I've hacked away at Vue projects (like a timer app for daily tasks) to figure out web-workers and browser storage work.
And I've gone through courses such as Django Channels + Angular + Docker to learn wtf a websocket is.
I even did some minor work on a
express admin app for a startup.
(Saying this fully aware of Python's packaging woes...)
A Simpler Web Stack🔗
If you've run code like
django-admin startproject mysite then fired up a webserver without understanding the magic of the background that is totally fine.
To get a grip on why
django does what it does, you can spend some time with
A simple bottle app that returns
hello world on a GET request might look like the following:
Running this code in a normal Python script (i.e.
python my_bottle_app.py) will run a server on your local machine.
Going to a browser with url http://localhost:8080/ should show you 'hello world'.
You can also use a program like
curl (or Python
requests) to fetch the data as an API request.
WTF is a
route or a
localhost or a
An Easier Web Stack🔗
Let's compare this to a
streamlit hello world.
To run this we need to use
streamlit run my_streamlit_app.py (you may need
python -m streamlit run ... to specify the current Python interpreter)
Then we should automagically get a browser window open with our 'hello world' displayed, styled, and part of a larger app with a hamburger menu that can change the theme and do some other things.
One major drawback is we don't get access to our code via API (see this github issue for my hack on adding API routes to your Streamlit app's
Bottle + HTMX🔗
To actually get cooking with gas, let's demo an interactive
If you don't want to see raw HTML, avert your attention now.
If you have played around with
flask, this app creation pattern and adding routes with decorators might be a little familiar.
Returning HTML strings with random
hx- element attributes is probably not familiar at all.
To be explicit, I've included 2 routes to serve the 2 necessary static files. Bottle does have the ability to serve static files from a directory on a wildcard route.
index.html holds the skeleton of the app.
Just including the interesting bits here:
At the top we have a standard
<script> tag to load in the file at
/htmx.js, which is the raw
htmx library (included as raw file for showcase, serve it gzipped / from cdn in production).
htmx magic comes in the
button is clicked, it will make a GET request to
It then takes the response from
/fun and injects it into the
innerHTML of the
#app element (the
div in this case).
All powered by
htmx using 3 HTML attributes!
Get Routes in Bottle + HTMX🔗
Next we have a route that handles GET requests to
bottle side of things we just want to return the HTML template for the "fun zone" (tm 🎉).
Which with syntax highlighting looks like:
The first element returned is a
textarea for inputting random text.
When the content of this
textarea changes, a POST request will be sent to
/fun (note the
hx-post="/fun"), and the response will be injected into the
innerHTML of the
Post Routes in Bottle + HTMX🔗
htmx can send data from our html widgets in a post request (see the docs for more understanding of request triggers such as with HTML forms).
bottle can then parse this request data and do something with it!
In this case, we'll return the message in reverse and announce how long the string is.
By returning HTML, it will get displayed nicely in the element specified by
In the case where there isn't any actual text, it will implicitly return None and won't update the display.
NOTE this doesn't use built in
bottle templating for simplicity.
bottle uses a similarly confusing global request object to
flask, but "Pay no attention to that man behind the curtain!"
Doing it with Streamlit🔗
bottle example has grown in complexity and "things to know about".
So far the concepts outside of Python (that all require coding attention!) are:
- Ports / Hostnames
- Server / wsgi loops
- URL routes
- HTTP request methods
And if we wanted to deploy this to the world we'd also want to know (and spend time on):
- CSS styling
- Static file deployment / bundling
- Gunicorn / Gevent / production wsgi serving
- Testing interaction between HTML and routes
What if we could shove all of that mountain of stuff onto the shoulders of
NOTE These are all awesome things to learn! The Odin Project is an awesome resource and community for learning the web parts that Python learners usually don't come into web apps with.
Porting the example to streamlit, we can reduce it from 2 files (
wsgi.py) to 1 (
streamlit_app.py) and can reduce the lines of code we need to understand from ~50 to ~15.
No more HTML... Replaced with:
No more Ports / server / routes... Replaced with:
streamlit run streamlit_app.py
No more HTTP requests or
htmx... Replaced with:
if st.button("Do Something Fun 🎉!"):
fun_input = st.text_area('')
This example didn't even show any effort to CSS on the
bottle app, but
streamlit gives it to us for free!
Additionally, the communication between frontend components and backend updates has to be tested by you. And then you have to test the business logic.
streamlit has over a dozen widgets in their input library with a team and community of developeres working on their speed, UI/UX, and resiliency.
Finally, deploying to streamlit with
streamlit run streamlit_app.py is a valid way to run the server.
Deploying with Streamlit Sharing in the cloud is even less to deal with, netting a free Continuous Deployment pipeline from github to the cloud.
streamlit isn't the simplest interactive web stack out there, it very well might be the easiest.
With a growing community, new web features are added all the time to
streamlit, but are accessible in a Pythonic way.
Are you with the majority of people who hear the word "state" and think about political geographies?
Spend some time with session_state since the
streamlit execution model is unique.
Soon your understanding of frontend
state will carry over to other projects!
Want to learn what query params / arguments are for? There's experimental support for getting and setting them with Python.
All that being said, if you need barebones interactivity with no dependencies, it is acheivable.
Created: June 7, 2023