Building a fullstack app with Flask and Htmx
codecapsules.io
codecapsules.io
Our operating theory was that using a JS FE framework was overkill for a CRUD app. So we went with stock Django and server-rendered HTML.
It became clear that parts of our UX would benefit greatly from client-side interactivity, which is what we eventually expected to happen. We sprinkled in HTMX (on top of our Jquery) on certain pages and it's been a breath of fresh air. We even built a nice Kanban board w/ HTMX and a Jquery plugin. Key benefits include:
1. When you're looking at a template the HTMX syntax makes it obvious what happens
2. HTML is rendered server-side in templates (imho where it should be) and is sent down the wire
3. We get a lot of stuff for free from Django (routing, back button handling, etc) that we don't need to build with a FE framework
Even though we're devs we believe the best code is no code (in the literal sense, not the de rigeur "No Code" sense). Maintenance costs are important to us. Getting stuff for free is important to us. Building for a specific use case is incredibly important to us.
With that in mind, the Django + sprinkled HTMX experiment has been pretty successful thus far.
Don't I need to spend a lot more effort using their original JS incantations and integrating that w/HTMX?
It's all very easy to wire in to a normal Django page to gradually add interactivity (say, partial updates to filter/sort a table to instead of full-page reload).
https://django-htmx.readthedocs.io/en/latest/middleware.html...
The examples give a fleshed-out version: https://github.com/adamchainz/django-htmx/blob/main/example/...
Though that's very slightly different than how I described it above, but it's basically the same idea; in this case you commonize the `main` block between the two contexts.
Or maybe I just got old!
regarding point 1:
1. When you're looking at a template the HTMX syntax makes it obvious what happens
I am trying to codify this design principle under the term "Locality of Behavior". I have written an essay on this idea here:
https://htmx.org/essays/locality-of-behaviour/
there are other newer libraries that exhibit this characteristic as well: tailwinds and alpine.js for example
Not that I'm saying HTMX is better (or worse) than react, but that's what is being discussed here.
Another advantage is there are some cool no-code solutions where you can build internal admin tooling on top of your API, which kind of gets you some similar benefits to the django admin interface system. I haven't really used this approach in production but it makes a lot of sense.
But in some cases this can definitely come with more overhead than a quick Rails or Django app, and if you have a very read-heavy traditional website and no other clients then that overhead might not make sense. Sounds like you've found a platform that works well for your use case.
You’re just casually glossing over the fact that now you’ve created a distributed system - state still exists server side but now there’s a distributed eventually consistent subset of state living in the user’s browser (i don’t mean trivial UI state like is the dark theme enabled, i mean application state).
And you’ve opened this can of worms only for the trade off that you get to move view templating from the server side to the client side.
There’s one case where there’s obvious clear benefits of choosing the client side rendering trade off and it’s if you’re building a client side app. Something that should be an intensive desktop app but you choose to deploy via browser instead. Maybe a music mixing or live DJ’ing app or a photo or video editor. Delivering these apps via web has some neat benefits in app distribution and cross platform accommodations. It wouldn’t be possible to build them without client side rendering so it’s a clear win.
Most web apps are not that though. Most are like you describe, a UI to some APIs. The state really lives server side - my bank account UI, my blog posts, my … etc etc For these cases, you can reduce complexity by ditching the client side rendering.
It all just depends on the product though, if the client-side interactions are minimal enough than I agree the server-rendered approach can be better. I was just saying I don't see this as the inherently better way, there are pros and cons to each approach.
Which tools are you thinking of? I know of react-admin, are there others?
return f"<tr>
<td>{title}</td>
<td>{author_name}</td>
<td>...."
Probably safer (and more maintainable) to just return a Jinja2 template instead.How do you work with components e.g. buttons in django + tailwind? Do you {% include %} them or are all of them uniquely designed?
If you haven't tried Rails lately because it's no longer cool, I encourage you to look again. I've tried many different frameworks, including Flask, but nothing touches Rails (IMHO) in terms of productivity.
After that I went through a number of Chris Oliver's GoRails tutorials (https://gorails.com) which I found really helpful too.
Things I liked were that it felt really lightweight, easy to setup, and basically just worked how the docs described.
However one thing I'm still trying to wrap my head around is how to better handle "intermediate" pieces of HTML i.e. partials. In SPAs you create a component that renders the template on the fly and is reused, but in HTMX you would need to hit an endpoint to get something like a partial.
This sort of had me pause because I'm so used to keeping my backend routes very "RESTy" (in the loose sense). But when using HTMX I was wondering if I need to make some routes like `/contact-form/modal` just to retrieve something like a success modal.
The article kinda displays what I mean with how all of the book routes return the buttons for the form. Also the `/get-book-row/<int:id>` and `/update/<int:id>` routes both return the same type of buttons. Those buttons seem prime candidates to be extracted into a common template of sorts.
Does that make sense? If so does anyone have a good example of a larger HTMX app which handles these scenarios?
https://github.com/talkpython/htmx-python-course
He wrote a small library extending Jinja that does what you're referencing here, I believe. It's a little different than Jinja macros and include (you can reference the github issues for a discussion on that).
https://github.com/mikeckennedy/jinja_partials
Maybe that's what you're looking for?
I also use Tailwind CSS and I found a way to make that work nicely with HTMX for CSS transitions which I wrote about [0].
[0] https://www.crocodile.dev/blog/css-transitions-with-tailwind...
Ideally: Monolithic, 1 Language, No need to write your own APIs. Just direct access from the View.
This tutorial is a pretty in-depth guide that incrementally adds more features to a flask app: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial...
By no means do you need to use everything in here, but I reference it from time to time. This is plenty:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return "<h1>hello world<h1>"
@app.route("/foo")
def foo():
return render_template("foo.html", vars={"foo": "bar"})
if __name__ == "__main__":
app.run()If you e.g. have a use case where your dependency-situation is one where you won't benefit from bottle being single-file (because you e.g. have a ton of other non-single file dependecies), then choosing it over the widely used Flask might not be show you any benefits at all.
For the record, I've used both, and Bottle is much faster to get started with. The docs are also much leaner.
For quick and dirty projects I’ve always wondered what a Python version would look like.
Maybe Jinja2 pages, a place to define extra code to be run or a custom ninja tag for longer sections of Python, and a binary to launch the whole thing as a server?
You mentioned "weekend projects", which indicates that you value development productivity. You want to be able to do more with your allotted time. The way we tend to envision a "simple stack" is to mean a combination of lighter and possibly specialized tools. But does that necessarily translate into quick development? Your choice of tools still relies on your experience with them individually.
I often see people advocating for microframeworks when this question comes up. As a long time user, I would say sure, but with the caveat that they come with very few batteries, and sometimes fewer opinions. To me this is also the promise of many future decisions to be made, by you. How much time does a single decision typically cost you?
Also, the size of your project's audience, doesn't imply that the app needs considerably less dependencies, than something more ambitious popularity wise. The difference usually lies in the infrastructure that will eventually specifically address scalability.
I think a good simple stack for week-end projects should aim to get you from install to development fairly quickly. Don't waste time making too many technical decisions beyond the choice of tools. Spend half an hour setting up a container, design your database, and get to work. If some other people have already spent time working out what's best in 90% of cases, use that experience first. Find out later that your case falls within the 10% that doesn't apply.
---
Now, for some more practical recommendations. I think Django's opinionated batteries make it a more compelling solution for "week-end" type projects. Does it matter that it installs megabytes on disk, of which you end-up barely using a few KBytes? For most cases I don't think it does.
My stack suggestions for week-end type projects on the back-end will be heavily Python-centric (except for Ruby-On-Rails):
- Django|RoR, Postgres: Opinionated batteries. Just hop on the train and simply let it drive you.
- Flask, Psycopg2, Postgres: when you're already comfortable replacing most things Django offers for free, with your own assortment of best-of-breed tools and practices (testing, templating, etc). Working with Psycopg2 implies that you're reasonably comfortable working directly with SQL, and willing to spend some additional time working out the various kinks that inevitably need addressing, when managing your own connections to the database.
- Flask|Django, Postgres, SQLAlchemy: Only if you already know SQLAlchemy. If you don't, just trust that you won't learn it in a week-end.
I thought HTMX was going to be that silver bullet that let me get away with never writing JavaScript again, but after using it in a few places, I'm a bit more selective in which projects I adopt it for:
If you're doing anything fancy before or after a request, you either have to use Javascript anyway, or pull in hyperscript as another dependency and then you also have to learn hyperscript. (Some of the demos on the HTMX site require hyperscript.)
Browser-native prompt dialogs send the contents of the prompt as a custom request header instead of POST data, which means you have to modify your backend routes, making HTMX much less plug-and-play. If there's a good reason for this design decision, it's not mentioned in the docs or examples.
If you already know how to use querySelector(), fetch(), and innerHTML in Javascript, HTMX isn't going to offer you much except cleaner-looking code at the cost of adding a dependency to the project.
Do you see any other scenarios where this stack setup struggles? If you were to design this how would you do it? Since I do think it is helpful in some cases like creating forms and basic navigation.
Liveview(HTMX analog) for anything with data and state that needs to go to and from the server; Alpine JS for client side interactions with ephemeral state like opening a modal.
Hopefully by staying close to HTML's semantics and design philosophy, using htmx doesn't introduce a lot of conceptual load on top of what is already there, and saves you complexity where the more elaborate client-side stuff isn't needed.
i'd add that stimulus is another great alternative for sprinkles of front-end interactivity without going all-in on js tooling and a js framework. it pairs nicely with rails 7's new turbo feature to provide a full server-side rendering experience, similar to htmx.
I’m not talking about frameworks, I’m talking about vanilla es6+ and occasionally leveraging npm the way they do Python package dependencies.
I don’t blame folks for this but solid devops exists now.
A lot of the pains around including modern js in a template driven web project have been worked out. The benefits including the cross browser compatibility, tree shaking and module importing and are there for the taking.
Form manipulation, Ajax behaviors—-using JS to liven up your app can be learned and implemented.
I hate having to set up a "modern" js toolchain when starting a new project. No matter what tools you choose it's always a huge hassle, and it never manages to integrate seamlessly with the backend.
What I like about HTMx and alpine.js is that first of all the set up is trivial. I just stick the js files in my static folder and stick a script tag in the header. Secondly they leverage exactly what the backend is good at: producing HTML. Getting the backend and frontend talking is trivial instead of a huge REST-y kludge.
With modern JS I need to write database models, REST views, REST serializers, components, routing logic, etc.
with HTMx and alpine I still need to write database models, views and templates but I get to fully skip the whole translation to and from REST (or whichever other API model you prefer). And in the end it's no less performant.
So yeah for hobby projects I definitely avoid modern JS, but it's not for no reason. The developer experience is just awful IMO.
If you're a PHP dev check Yoyo Framework. It's like Laravel Livewire without all the bloat from Laravel.
Also possible to use HTMx so you avoid JavaScript as much as possible,
I think the animosity towards "over use" by some of the core community is misguided. The continued work that goes towards improving extensibility and features is surely a sign that someone finds it worth contributing towards. And the number of projects on Github or Django Packages that hook into the admin have to be written by somebody.
I think the right comparison here is to Flask-Admin, though, not Flask itself, and I’ll just say that the app I have to deal with that’s built on Flask-Admin is a nightmare, certainly no better than an equivalent app using Django Admin would be.
Re the Django ORM, SQLAlchemy is certainly better in many ways, but the Django ORM is pretty good these days (and has been for a while IMO).
If I know I’ll have complex database requirements that Django can’t easily handle, I’d much rather use Pyramid + SQLAlchemy than Flask.
It really is not.
IME, this stack has been pretty simple both at-scale and at-home. At scale, my experience is mostly using it in REST APIs serving a react frontend.
At-home, I've used it to setup numerous graphql services (with the graphene plugin).
Admittedly, there's probably 100 different ways to configure anything wrong, especially for any framework written in Python. But once you witness a "golden path", and stick to it, it's pretty straightforward. (The Flask docs have improved a lot in this regard, in the last few years)
Also, there’s nothing about these apps that requires the alleged flexibility of Flask, so there’s a lot of issues with dependencies and integrating extensions. That’s a lot of extra work and probably weakens security for no benefit at all.
Upgrading a big Django app is much simpler IME.
These things are difficult both to depend upon externally and to maintain, due to the dependency/upgrade issue you mentioned.
For an example in a similar ecosystem: at a previous company, at one time we used SQLalchemy-continuum for audit tables. We found some bugs and were not able to upstream them. When we wanted to upgrade SLQLAlchemy to X.Y, we were stuck with this dependency that required SQLAlchemy X.Y-1.
After a few years, we realized it was cheaper to cook up a SQLAlchemy-Continuum subset that met our needs, so it'd be easier to keep in line with SQLAlchemy.
It's a very different mindset from Django; I think there's an appropriate context for each.
I personally wouldn't want my web framework to implement login/password/email-reset for me, although I understand Django offers it to varying degrees. It's a space where "best practice" has evolved dramatically in the past decade.
[1]: https://flask-oidc.readthedocs.io/en/latest/ they looked a lot like this, fwiw. Although to your point, this too, hasn't been updated in 5 years. :)
The main point though is that I'm not interested in being on the forefront of login, it is not a differentiator (unless project is a laggard). In fact being five years behind bleeding-edge sounds a bit perfect.
If your problem and requirements are such that you will always be outside the part of the solution space that SPAs are meant for, HTMX and progressive enhancement are great. If not, you may discover the transition like a boat discovers the transition between water and ice.
If you don't immediately know the shortest path from your requirements to that transition, but you're sure that SPAs are Bad and this is Good, please take a moment to consider why you think that.
That seems like a very very strong claim. "You should do an SPA in case you ever need to make an SPA". Like what do you think actually needs to be an SPA?
Here's my list of popular sites that don't need to be an SPA but are anyway
* Reddit, nothing about it lends itself to being an SPA at all.
* Facebook, I think they went the SPA route in order to make it more difficult for ad blockers
* Netflix (don't know how related this choice is to DRM constraints honestly)
* paypal
* gmail
Here's my list of sites that do need to be an SPA, or are at least easier if you make them as an SPA.
* Google maps
* Google docs
* Photopea
* Discord (This is almost all achievable with something like HTMX, save the webrtc stuff, but I think it would be more of a pain and would rely on a lot of complicated session management stuff happening on the server)
* AirBnb, which I'll give a pass since it makes such heavy use of maps.
My take away is that if you're reaching for the standard SPA toolkit you might be better off using something like QT or Godot engine, writing native code, and compiling it for the web. Anything that uses maps or rich text editing probably should be done as an SPA, although if you can make it so that just the one component is "rich" you should probably do that.
Using more complicated components can be a reasonable idea, like using a graphing widget or a maps widget, but you can do those things without making your entire app an SPA.
I just don't buy the "you might need to make your app an SPA some day to you might as well take on the technical burden from that decision now" idea. Very very few projects need to be an SPA, and when you have one it's probably pretty obvious.
I'm not selling that idea. "Boil the ocean now in case you want a cup of tea tomorrow" is a fantastically effective way to fail.
>> If your problem and requirements are such that you will always be outside the part of the solution space that SPAs are meant for
> That seems like a very very strong claim.
By that wordy garbage I was trying to contrast the very extreme, where HTMX is an obvious fit, with points further down the spectrum where it's not so clear. You could go full SPA on an HTMX-shaped problem, if you wanted to, or full progressive enhancement on an SPA-shaped problem; I can't think of any technical thing that would stop you. There would just be consequences and it's up to you if they're worth it.
> Like what do you think actually needs to be an SPA?
I think a move to SPA gets more attractive when you have 1) ad-hoc client-only state 2) that shouldn't be explicitly reflected in your server-side data model 3) produced without coordination by different independently-enhanced components sharing a page 4) which causes unexpected interactions between them.
SPA is not the only way to solve that, solving it is not the only benefit of SPA, you could just ignore it and accept the combinatorial growth of your state space, a hundred other caveats. It's an engineering question which should get an engineering answer maximizing benefit by balancing tradeoffs, but IMO that's not the treatment it usually gets.
That's a lot of what I was thinking about when I was thinking if discord really needed to b an SPA, all that ad-hoc client state would need to be reflected on the server.
One thing I noticed after making a small FastAPI/HTMX app was that I missed the hot reload of React. Also I noticed when I deployed to a live version of the site I found myself needing to hard refresh my browser because my assets didn't have hashes appended to bust the cache.
Just curious if anyone has some cool setups :) It would be awesome if there was a way to integrate ParcelJS as it has a nice reload server.
I was hoping to look for a way to get auto reload to work while being served by your Flask/FastAPI server. Maybe it's not a big deal to dev using "reload" but have your prod app served by Flask/FastAPI.
[0] and [1] are relevant sections of a much longer guide that go into more detail about the tradeoffs.
[0] https://www.saaspegasus.com/guides/modern-javascript-for-dja...
[1] https://www.saaspegasus.com/guides/modern-javascript-for-dja...
Vue positions itself for anything from jQuery replacement all the way to a full SPA and everything in between.
Anything works that way as long as it doesn't come with a built in router and you are careful not to accidentally bundle two different versions of your runtime.
- Infinite scroll: HTMX detects the specified viewpoint and requests the next batch of content. The backend returns only the rendered partial HTML content that HTMX appends to the bottom of the page.
- Next page of a paginated content: if the users clicks on the 'Next page' button, the backend returns only the rendered HTML content of the next page, HTMX replaces only this part of the page.
- Form submission/validation: you can skip most of the frontend validation and use the backend for it. If the form has an error, HTMX replaces the new rendered form with the inlined error messages. Otherwise it can forward the user to the next page or shows some success message.
- Inline edit: e.g. click on the row in a table to load a small form just for that line. After the submission the backend returns the updated rendered table.
- Chained select input elements: when the user selects something in the first dropdown, it triggers an HTMX request that loads just the next select element that reflects the value of the first one.
- Background task with progress bar: just return a delayed, automatically triggered HTMX request from the backend until the task is finished. The status (e.g width) of the progress bar is also calculated on the backend.
- Search preview: with the builtin debounce filter trigger a search request that returns the search suggestions.
- Multiple small(er) updates on the page: one HTMX request can return multiple HTML partials that updates any part of the page. If you couple it with the builtin automated polling/websocket, you can build a live dashboard easily.
Of course you can do any of these with a SPA framework coupled to the backend API. But with HTMX you don't have to write a single line of JS, just put some htmx-something="..." attributes in the backend template files.
The main advantage I see with this approach is you'll have only one set of models/actions in the server, instead of models in server + models in client. The logic won't be spread between client and server, which make for easier testing among other benefits. For larger apps, this is a significant win.
The switch from django templates was painful and a tremendous learning curve, so I get the value proposition of drop-in interactivity tools like HTMx. But when you compare the two frontend ecosystems (javascript vs htmx) it's night and day. The support, the DX tooling, the variety of libraries... that's not to say HTMx couldn't one day mature to a comparable place, but it seems unlikely, and in the meantime investing in "eating the elephant" by learning a JS framework seems worthwhile for anyone planning to spend a significant amount of time building frontends
I eventually realized I had a typo in a template that was all too easy to overlook. Since there wasn't any tooling to catch it, it behaved like an html attribute and did nothing.
I seriously don't know that I would want to go back to such an anemic dev experience unless it is for a toy project.
HTMx is more about simplicity - an easy alternative to AJAX that doesn't require a full-blown front-end framework. It isn't really designed for optimized real-time communication and long-running connections, but you could probably use it for that if you wanted.
With LiveView, it seems to be just sending application state up and down the websocket wire, does HTMx not do this as well if it was setup that way with WS?
it tries to extend HTML as a hypermedia, which makes it more general (it can be used with any backend that produces HTML) but also more work to achieve things that, in live view, work magically
htmx is just html++: any element can make any sort of HTTP request (GET, DELETE, etc.) based on any event and replace any other element in the DOM.
much simpler and "dumb" when compared w/ liveview, with no particular server-side implementation implied
LiveView tries to replicate the architecture of a native app toolkit and slap a network boundary in the middle of it, whereas htmx tries to keep the programming model of Web 1.0 apps while radically expanding the user experience possibilities