What are the components of FastHTML, and how do they work together? What are some key things I need to understand to write idiomatic FastHTML apps?
Based on the documentation provided, here are the key components of FastHTML and how they work together to create web applications:
- Core Components:
- FastHTML class: This is the main application class, inheriting from Starlette. It sets up the ASGI app with additional features.
- Route decorators: Used to define request handlers, e.g. @rt("/") or app.route("/").
- FT (FastTags): A system for creating HTML elements using Python functions/objects. These are automatically converted to HTML when needed.
- Database Integration:
- FastHTML integrates with databases supporting the MiniDataAPI spec. The example uses SQLite with FastLite.
- Database tables can be easily created, queried, and updated using this API.
- Request Handling:
- Handlers can receive various arguments automatically, including form data, path parameters, and special objects like 'session' or 'auth'.
- Dataclasses can be used to automatically parse form data into objects.
- HTML Generation:
- Instead of writing HTML strings, you create trees of FT objects which are automatically converted to HTML.
- Common HTML tags are available as Python functions (e.g., Div, Form, Input).
- Custom components can be created easily as Python functions returning FT objects.
- HTMX Integration:
- FastHTML is designed to work seamlessly with HTMX for dynamic updates.
- Many FT objects have attributes that correspond to HTMX attributes (e.g., hx_post, hx_swap).
- Middleware and Security:
- Beforeware: Functions that run before request handlers, useful for tasks like authentication.
- Session handling: Secure, signed sessions stored in browser cookies.
- Static File Handling:
- Built-in support for serving static files.
- CSS Integration:
- Easy integration with CSS frameworks, with built-in support for PicoCSS.
Key things to understand for idiomatic FastHTML apps:
- Use FT objects instead of HTML strings for composing your UI.
- Leverage HTMX for dynamic updates by using appropriate attributes on FT objects.
- Use the database API for data operations rather than writing raw SQL.
- Create reusable components as Python functions that return FT objects.
- Use Beforeware for consistent pre-processing of requests (e.g., authentication).
- Utilize dataclasses for handling form submissions.
- Take advantage of FastHTML's automatic type conversion for request parameters.
- Use the session object for secure client-side storage.
- Organize your application logic into appropriate route handlers.
- Leverage FastHTML's integration with CSS frameworks for styling.
By understanding these components and principles, you can create efficient, readable, and maintainable web applications with FastHTML.
<div class="w-[350px] bg-white rounded-lg shadow-lg overflow-hidden">
<div class="bg-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800">Card Title</h2>
<p class="text-sm text-gray-600 mt-1">Card Description</p>
</div>
<div class="p-6">
<p class="text-gray-700">Card content goes here. You can add any text or elements.</p>
</div>
<div class="bg-gray-100 p-6">
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">
Action
</button>
</div>
</div>
Can you recommend a way to encapsulate this kind of card as a FastHTML Python function that can be used as a component?
Certainly! You can create a reusable card component in FastHTML by defining a Python function that returns the appropriate FT objects. Here's how you could encapsulate this card as a FastHTML component:
from fasthtml.common import *
def Card(title, description, content, action_text="Action", width="350px"):
return Div(
Div(
H2(title, cls="text-xl font-bold text-gray-800"),
P(description, cls="text-sm text-gray-600 mt-1"),
cls="bg-gray-100 p-6"
),
Div(
P(content, cls="text-gray-700"),
cls="p-6"
),
Div(
Button(
action_text,
cls="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
),
cls="bg-gray-100 p-6"
),
cls=f"w-[{width}] bg-white rounded-lg shadow-lg overflow-hidden"
)
# Usage example:
@rt("/")
def get():
return Titled("Card Example",
Card("My Card", "This is a sample card", "Here's some content for the card.")
)
This Card
function encapsulates the structure and styling of the card component. Here's a breakdown of how it works:
-
The function takes parameters for the card's title, description, content, action text, and width. This allows for customization when using the component.
-
It uses FastHTML's FT objects (like
Div
,H2
,P
,Button
) to structure the card. -
CSS classes are applied using the
cls
parameter, which FastHTML will convert toclass
in the resulting HTML. -
The entire card is wrapped in a
Div
with the outer styling applied.
To use this component in your FastHTML application, you would simply call the Card
function with the appropriate parameters. For example:
@rt("/")
def get():
return Titled("Card Example",
Card("My Card", "This is a sample card", "Here's some content for the card.")
)
This approach allows you to:
- Reuse the card component across your application.
- Easily modify the card's structure or styling in one place.
- Create multiple cards with different content by just calling the function with different parameters.
Remember that this example assumes you're using Tailwind CSS classes. If you're using a different CSS framework or custom styles, you'd need to adjust the class names accordingly.
Also, if you find yourself frequently using components with similar structures but different content, you might want to create a more generic Card
component that accepts FT objects as children, allowing for even more flexibility in its usage.
What are some shortcuts for getting request info in handlers?
FastHTML provides several convenient shortcuts for accessing request information in handlers. These shortcuts make it easier to retrieve common request data without having to manually parse the request object. Here are some key shortcuts and methods for getting request info in FastHTML handlers:
- Automatic parameter injection: FastHTML automatically injects certain parameters into your handler functions based on their names. This allows you to directly access various request-related information without explicitly parsing the request object.
Edit from Jeremy: Note that actually the last 4 params here all require a type annotation, which Claude forgot to add here. So e.g. it should be
query_param:str
, not justquery_param
.
@rt("/example")
def get(
auth, # Current username (if authenticated)
session, # Starlette session object
request, # Starlette request object
htmx, # HTMX headers
app, # FastHTML app object
query_param, # Specific query parameter
path_param, # Path parameter (if defined in route)
form_field, # Form field value
custom_header, # Custom header value
):
# Handler implementation
pass
- Type annotations for automatic parsing: You can use type annotations on your handler parameters to automatically parse and convert request data to the specified type.
@rt("/example/{id}")
def get(id: int, age: int, name: str):
# 'id' is parsed from the URL path
# 'age' and 'name' are parsed from query parameters or form data
pass
- Dataclass injection: You can define a dataclass that represents your expected request data, and FastHTML will automatically instantiate it with the appropriate values.
from dataclasses import dataclass
@dataclass
class UserData:
name: str
age: int
@rt("/user")
def post(user: UserData):
# 'user' is automatically instantiated from form data or JSON payload
pass
Edit from Jeremy: Actually it doesn't have to be a dataclass -- any class will work fine.
- Request object methods: While not a shortcut per se, the Starlette request object (which FastHTML uses) provides several convenient methods for accessing request data:
@rt("/example")
def get(request):
query_params = request.query_params
headers = request.headers
cookies = request.cookies
client_host = request.client.host
# ... and more
- Session access:
You can easily access the session data by including a
session
parameter in your handler:
@rt("/example")
def get(session):
user_id = session.get('user_id')
# ... use session data
- HTMX-specific information:
For HTMX requests, you can access HTMX-specific headers by including an
htmx
parameter:
@rt("/example")
def get(htmx):
if htmx.is_htmx:
# This is an HTMX request
trigger = htmx.trigger
target = htmx.target
# ... handle HTMX-specific logic
- Partial string matching for request and session: You can use any string prefix of 'request' or 'session' to get those objects:
@rt("/example")
def get(req, sess): # 'req' for request, 'sess' for session
# Use req and sess as you would use request and session
pass
These shortcuts and methods allow you to write more concise and readable handler functions by providing easy access to various aspects of the request. They help reduce boilerplate code and make your FastHTML applications more maintainable and easier to understand.
This is very helpful, thank you. How do I use HTMX with FastHTML? Can you show me a minimal but complete working example, perhaps a simple counter? The count needs to be stored in a hidden var, and the hidden var and output can be in a div together in a form.
Certainly! I'd be happy to provide a minimal but complete working example of using HTMX with FastHTML to create a simple counter. This example will demonstrate how to use a hidden variable to store the count and update it using HTMX.
Edit from Jeremy: This example runs fine, but read on below for a few steps of minor improvements we make, which might help you better understand FastHTML and HTMX.
from fasthtml.common import *
app, rt = fast_app()
def counter_form(count):
return Form(
Div(
Hidden(id="count", value=count),
Div(f"Count: {count}", id="count-display"),
Button("Increment", hx_post="/increment", hx_target="#counter-container", hx_swap="innerHTML")
),
id="counter-container"
)
@rt("/")
def get():
return Titled("HTMX Counter",
Main(
H1("HTMX Counter Example"),
counter_form(0)
)
)
@rt("/increment")
def post(count: int):
new_count = count + 1
return counter_form(new_count)
if __name__ == "__main__":
serve()
Let's break down this example:
-
We import everything we need from
fasthtml.common
. -
We create our FastHTML app and route decorator using
fast_app()
. -
We define a
counter_form
function that creates the form containing:- A hidden input to store the count
- A div to display the count
- A button to increment the count
-
The main route (
/
) renders the initial page with the counter set to 0. -
The
/increment
route handles the POST request to increment the counter:- It receives the current count as an integer (FastHTML automatically converts it)
- It increments the count
- It returns the updated counter form
-
HTMX is used in the following ways:
hx_post="/increment"
on the button triggers a POST request to /increment when clickedhx_target="#counter-container"
specifies that the response should update the element with id "counter-container"hx_swap="innerHTML"
tells HTMX to replace the inner HTML of the target element
To run this example:
- Save the code to a file (e.g.,
counter_app.py
) - Run it using Python:
python counter_app.py
- Open a web browser and navigate to
http://localhost:5001
(or whatever port FastHTML is using)
You should see a page with a counter and an "Increment" button. Clicking the button will increment the counter without reloading the entire page.
This example demonstrates several key concepts:
- Using FastHTML to create HTML structures (like forms and buttons)
- Integrating HTMX attributes directly into FastHTML components
- Handling both GET and POST requests
- Updating parts of the page dynamically using HTMX
- Using hidden inputs to maintain state
- Automatic type conversion of request parameters in FastHTML
Remember that FastHTML includes HTMX by default, so you don't need to add any additional script tags to your HTML to make this work.
That's working great! Some suggestions:
- Remove the
if name==
line sinceserve
already has that - Remove the H1 since Titled already adds it
- Move the button out of the div since it doesn't change, so the button can go directly in
get
. That means theForm
will also be inget
.
Excellent suggestions! You're absolutely right. These changes will make the code more efficient and idiomatic. Let's implement these improvements:
from fasthtml.common import *
app, rt = fast_app()
def counter_display(count):
return Div(
Hidden(id="count", value=count),
Div(f"Count: {count}", id="count-display"),
id="counter-container"
)
@rt("/")
def get():
return Titled("HTMX Counter",
Form(
counter_display(0),
Button("Increment", hx_post="/increment", hx_target="#counter-container", hx_swap="innerHTML")
)
)
@rt("/increment")
def post(count: int):
return counter_display(count + 1)
serve()
Let's go through the changes and their benefits:
-
Removed the
if __name__ == "__main__":
block:- As you pointed out,
serve()
already includes this check, so we can call it directly at the end of the file.
- As you pointed out,
-
Removed the
H1
:- The
Titled
function already adds anH1
with the title, so we don't need to add another one.
- The
-
Moved the
Button
andForm
to theget
function:- This is more efficient because the button doesn't change, so it doesn't need to be re-rendered on each increment.
- The
Form
is now in theget
function, which makes the structure of the page clearer.
-
Renamed
counter_form
tocounter_display
:- This better reflects its new role of just displaying the counter, not including the button.
-
Simplified the
post
function:- It now only returns the updated
counter_display
, which is all that needs to change.
- It now only returns the updated
These changes make the code more efficient and easier to understand. The HTMX interaction remains the same: clicking the "Increment" button will send a POST request to /increment
, and the response will update only the counter display.
This revised version demonstrates a more idiomatic use of FastHTML and HTMX, where:
- Static elements (like the button) are defined once in the initial page render.
- Only the dynamic parts (the counter display) are updated in HTMX responses.
- The code structure more clearly separates the initial page rendering from the dynamic updates.
This approach is more scalable and maintainable, especially as your application grows more complex. It's a great foundation for building more advanced HTMX-powered interfaces with FastHTML.
Edit from Jeremy: The example below is just very minor and entirely optional changes -- the previous version works fine, but this version I've edited based on what I think are slightly nicer approaches; read on to see why Claude thinks so (and I agree).
from fasthtml.common import *
app, rt = fast_app()
def Counter(count):
return Div(
Hidden(id="count", value=count),
Div(f"Count: {count}"),
id="counter-container")
@rt("/")
def get():
return Titled("HTMX Counter",
Form(
Counter(0),
Button("Increment", hx_post=increment, hx_target="#counter-container"))
)
@rt
def increment(count: int): return Counter(count + 1)
serve()
I've made some minor changes above to make it more idiomatic -- what changes do you see, and how do the fit the FastHTML idiomatic style better?
You've made some excellent changes that indeed make the code more idiomatic for FastHTML. Let's break down these changes and discuss how they improve the code:
-
Naming Convention:
- You've renamed
counter_display
toCounter
. This follows the convention of using PascalCase for component names in FastHTML, similar to React components. This makes it clear thatCounter
is a reusable component.
- You've renamed
-
Simplified HTMX attributes:
- You've removed the
hx_swap="innerHTML"
attribute. This is more idiomatic because FastHTML/HTMX defaults toinnerHTML
swapping, so it's not necessary to specify it explicitly unless you want a different behavior.
- You've removed the
-
Route definition for
increment
:- You've used
@rt
without specifying a path for theincrement
function. This is a nice FastHTML feature where it automatically uses the function name as the route path. Soincrement
will be available at/increment
.
- You've used
-
Simplified
increment
function:- The function is now more concise, directly returning the
Counter
component with the incremented count. This aligns well with FastHTML's preference for compact, expressive code. (Note from Jeremy -- actually all I did was reformatting to put the function on one line; Claude had already directly returned the component!)
- The function is now more concise, directly returning the
-
Removed unnecessary
id
from innerDiv
:- In the
Counter
component, you've removed theid
from the innerDiv
that displays the count. This is good because theid
wasn't being used for targeting, and it's generally better to minimize the use ofid
attributes unless necessary.
- In the
-
Direct use of
hx_post
:- Instead of using a string
"/increment"
forhx_post
, you're directly referencing theincrement
function. This is a very idiomatic FastHTML approach, as it allows for better refactoring and maintains the DRY (Don't Repeat Yourself) principle.
- Instead of using a string
These changes collectively make the code more concise, more aligned with FastHTML conventions, and easier to maintain. They demonstrate a good understanding of FastHTML's features and idioms, such as:
- Using component-style functions (like
Counter
) to encapsulate reusable UI elements. - Leveraging FastHTML's automatic route naming.
- Taking advantage of default behaviors (like HTMX's default
innerHTML
swapping) to reduce unnecessary code. - Using direct function references in HTMX attributes for better code organization and refactoring support.
This version of the code is an excellent example of idiomatic FastHTML usage, showcasing how to create efficient, maintainable, and expressive web applications with minimal boilerplate.
Amazing AI... and amazing way to teach AI using llms.txt too! Thanks Jeremy