Abstract
Want to bring hypermedia into your web design workflow, ditching the complexity of JSON-over-HTTP for a more RESTful approach?
Create and design your web application with htmx and spark joy in your design process. Splash in a little Tailwind CSS, too. (Ssshh. You're a Full-Stack Developer now.)
Notes
These notes are for a tutorial presented at PyCon US 2023. In case you missed something during the tutorial (or happened upon this page out of interest in the subject), you may be able to find additional information here, or you can reach out to me individually, if something is missing.
Repositories
Music Binder
This repository was used during the tutorial to highlight some htmx and Tailwind features.
https://github.com/tataraba/musicbinder
Simple Site
If you want to see how to build a site like Music Binder from the ground up (including a little background on FastAPI), then this repo is for you.
The documentation is split into chapters that introduce each section and highlight features during each section of the build.
This repo was used during a workshop with the San Diego Python User Group on April 1, 2023.
Perfect if you want to follow along at your own pace.
https://github.com/tataraba/simplesite
Codespaces
In case you want to test either of the repos above, the easiest way to test things out is to use GitHub Codespaces
Instructions on how to do this are in the repo, but it mostly involves pressing a button and waiting a bit.
This opens up the code in your browser window in a dedicated, containerized cloud environment.
No need to install dependencies or clone/copy anything locally.
If you use VSCode, you can also open it up on your desktop application.
Stack
For the Music Binder app, I used the following libraries:
- FastAPI
- Modern, high-performance web framework
- Jinja
- Extensible html templating engine
- pytailwindcss
- A python wrapper for the standalone TailwindCSS CLI
- htmx
- Allows for modern browser features directly from html
- TinyDB
- Small document-based library utilizing local json file
That last one is meant to provide a thin database layer that can easily be replace by more feature-rich ORMs or ODMs, in case you want to adapt this for a real-world use case.
FastAPI Stuff
Templates
FastAPI provides a way to create Jinja template response with this command:
from fastapi.templating import Jinja2Templates
However, in the tutorial, we use a library called jinja2-fragments
as a drop-in replacement for Jinja2Templates
.
Instead, we import Jinja2Blocks
and otherwise use it the same way.
from jinja2_fragments.fastapi import Jinja2Blocks
Now we can create a templates
object which points to the directory where the templates are kept, something like:
templates = Jinja2Blocks(directory="path/to/templates")
Routes
Most web frameworks have a way to create routes/views/endpoints. These endpoints contain the logic that generates a response when a user visits a certain url in their browser.
Simple FastAPI applications typcially show these endpoints being defined all in the same place where the app is created (main.py
).
The Music Binder repo chooses to put all routes in one place (routes.py
) and then registers them all in main.py
where the app is instantiated.
In routes.py
you should see:
router = APIRouter()
Each subsequent route/view/endpoint is added to this instance of APIRouter
and is then "registered" in main.py
:
app.include_router(router)
Lifespan
One common task when starting up an application is providing "on startup" and/or "on shutdown" events, typically database operations or other build-dependent.
Very recently, FastAPI introduced an elegant way to deal with this by using asynccontextmanager
from the standard library.
In practice it looks something like this:
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup event
start_database()
yield
# Shutdown event
close_database()
This context manager can then be used to define the lifespan parameter of the FastAPI
app.
In Music Binder and Simple Site, it is used to run the tailwindcss
build command, just in case you forgot to build/compile the css prior to starting the app.
TailwindCSS Stuff
Perhaps the most important resource here, is the Tailwind documentation itself. It is probably more useful than anything I can type here.
I want to highlight two aspects of it, though.
DRY?
Using Tailwind might mean a paradigm shift in how you think about css. Since you are mostly spending your time in html, you start seeing a lot of patterns being repeated.
You may wonder if it might be a good idea to create your own custom classes to reduce how much code you see "repeating" (must... stay... DRY!!!)
But before you go down that road, read this:
Reusing Styles (TailwindCSS documentation)
If you think you need to create custom classes, keep this in mind:
Components and template partials solve this problem much better than CSS-only abstractions because a component can encapsulate the HTML and the styles. Changing the font-size for every instance is just as easy as it is with CSS, but now you can turn all of the titles into links in a single place too.
Classes
The second thing is not to get overwhelmed with the Tailwind classes. They tend to be easy to pick up, over time, but the documentation is pretty dang awesome.
Utilize the Search for either the Tailwind class your looking for, or the CSS property you are curious about.
And, if you use VSCode, use the Tailwind extension (I believe the one for PyCharm is already installed).
This will also help you tremendously.
Build
Okay, fine, a third thing. I pretty much always have the build command set to a watcher, which compiles on save. Sure, it's set to build on app startup, but having that immediate feedback while you're working is invaluable.
tailwindcss -i path/to/input.css -o path/to/output.css --watch
htmx
Last, but not least, don't forget to look at the essays on the htmx website:
These provide a lot of the context and reasoning behind this particular approach to building applications, and there are some very compelling cases.
I highly recommend this one:
A Response To "Have Single-Page Apps Ruined the Web?"
Again, usage is very straightforward. You just need to add the appropriate htmx attribute to most any html element, look for that particular request in your application (using the Request header), and send an appropriate response.
From htmx documentation:
<button hx-post="/clicked"
hx-trigger="click"
hx-target="#parent-div"
hx-swap="outerHTML"
>
Click Me!
</button>
“When a user clicks on this button, issue an HTTP POST request to ‘/clicked’ and use the content from the response to replace the element with the id parent-div in the DOM”
Production Ready
I've been asked if htmx is production ready.
Now, I'm just some lowly Business Analyst that plays around with python at the wee hours of the night, so don't take my word for it.
But if you're looking for a real world example of using htmx in a production environment, look no further than the DjangoCon EU 2022 talk given by David Guillot from Contexte:
From React to htmx on a real-world SaaS product: we did it, and it's awesome!
Some highlights (which you can also find over at htmx.org):
- The effort took about 2 months (with a 21K LOC code base, mostly JavaScript)
- No reduction in the application’s user experience (UX)
- They reduced the code base size by 67% (21,500 LOC to 7200 LOC)
- They increased python code by 140% (500 LOC to 1200 LOC), a good thing if you prefer python to JS
- They reduced their total JS dependencies by 96% (255 to 9)
- They reduced their web build time by 88% (40 seconds to 5)
- First load time-to-interactive was reduced by 50-60% (from 2 to 6 seconds to 1 to 2 seconds)
- Much larger data sets were possible when using htmx, because react simply couldn’t handle the data
- Web application memory usage was reduced by 46% (75MB to 45MB)
Of course, your use case may vary, but I'd say that there are plenty of benefits that you would need to consider when making this choice.
MUSIC!
Oh, one last thing.
I'm not sure if this was the only PyCon tutorial to ever have an associated playlist...
But if you're down for some indie music that includes some brooding ballads, melancholy musings, instrumental interludes, and other strange things...
You couldn't do worse than checking out my 4.5 hour Music Binder playlist: