Skip to content

Instantly share code, notes, and snippets.

@jph00
Last active November 18, 2024 00:18
Show Gist options
  • Save jph00/9559b0a563f6a370029bec1d1cc97b74 to your computer and use it in GitHub Desktop.
Save jph00/9559b0a563f6a370029bec1d1cc97b74 to your computer and use it in GitHub Desktop.
Understanding FastHTML Components and Architecture - a Claude Conversation

Understanding FastHTML Components and Architecture

πŸ§‘ human (Aug 26, 2024, 03:52 PM)

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?

πŸ€– assistant (Aug 26, 2024, 03:52 PM)

Based on the documentation provided, here are the key components of FastHTML and how they work together to create web applications:

  1. 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.
  1. 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.
  1. 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.
  1. 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.
  1. 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).
  1. Middleware and Security:
  • Beforeware: Functions that run before request handlers, useful for tasks like authentication.
  • Session handling: Secure, signed sessions stored in browser cookies.
  1. Static File Handling:
  • Built-in support for serving static files.
  1. CSS Integration:
  • Easy integration with CSS frameworks, with built-in support for PicoCSS.

Key things to understand for idiomatic FastHTML apps:

  1. Use FT objects instead of HTML strings for composing your UI.
  2. Leverage HTMX for dynamic updates by using appropriate attributes on FT objects.
  3. Use the database API for data operations rather than writing raw SQL.
  4. Create reusable components as Python functions that return FT objects.
  5. Use Beforeware for consistent pre-processing of requests (e.g., authentication).
  6. Utilize dataclasses for handling form submissions.
  7. Take advantage of FastHTML's automatic type conversion for request parameters.
  8. Use the session object for secure client-side storage.
  9. Organize your application logic into appropriate route handlers.
  10. 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.

πŸ§‘ human (Aug 26, 2024, 03:53 PM)

<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?

πŸ€– assistant (Aug 26, 2024, 03:53 PM)

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:

  1. The function takes parameters for the card's title, description, content, action text, and width. This allows for customization when using the component.

  2. It uses FastHTML's FT objects (like Div, H2, P, Button) to structure the card.

  3. CSS classes are applied using the cls parameter, which FastHTML will convert to class in the resulting HTML.

  4. 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:

  1. Reuse the card component across your application.
  2. Easily modify the card's structure or styling in one place.
  3. 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.

πŸ§‘ human (Aug 26, 2024, 03:53 PM)

What are some shortcuts for getting request info in handlers?

πŸ€– assistant (Aug 26, 2024, 03:53 PM)

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:

  1. 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 just query_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
  1. 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
  1. 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.

  1. 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
  1. 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
  1. 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
  1. 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.

πŸ§‘ human (Aug 26, 2024, 04:00 PM)

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.

πŸ€– assistant (Aug 26, 2024, 04:00 PM)

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:

  1. We import everything we need from fasthtml.common.

  2. We create our FastHTML app and route decorator using fast_app().

  3. 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
  4. The main route (/) renders the initial page with the counter set to 0.

  5. 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
  6. HTMX is used in the following ways:

    • hx_post="/increment" on the button triggers a POST request to /increment when clicked
    • hx_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:

  1. Save the code to a file (e.g., counter_app.py)
  2. Run it using Python: python counter_app.py
  3. 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.

πŸ§‘ human (Aug 26, 2024, 04:04 PM)

That's working great! Some suggestions:

  • Remove the if name== line since serve 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 the Form will also be in get.

πŸ€– assistant (Aug 26, 2024, 04:04 PM)

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:

  1. 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.
  2. Removed the H1:

    • The Titled function already adds an H1 with the title, so we don't need to add another one.
  3. Moved the Button and Form to the get 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 the get function, which makes the structure of the page clearer.
  4. Renamed counter_form to counter_display:

    • This better reflects its new role of just displaying the counter, not including the button.
  5. Simplified the post function:

    • It now only returns the updated counter_display, which is all that needs to change.

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.

πŸ§‘ human (Aug 26, 2024, 04:08 PM)

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?

πŸ€– assistant (Aug 26, 2024, 04:08 PM)

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:

  1. Naming Convention:

    • You've renamed counter_display to Counter. This follows the convention of using PascalCase for component names in FastHTML, similar to React components. This makes it clear that Counter is a reusable component.
  2. Simplified HTMX attributes:

    • You've removed the hx_swap="innerHTML" attribute. This is more idiomatic because FastHTML/HTMX defaults to innerHTML swapping, so it's not necessary to specify it explicitly unless you want a different behavior.
  3. Route definition for increment:

    • You've used @rt without specifying a path for the increment function. This is a nice FastHTML feature where it automatically uses the function name as the route path. So increment will be available at /increment.
  4. 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!)
  5. Removed unnecessary id from inner Div:

    • In the Counter component, you've removed the id from the inner Div that displays the count. This is good because the id wasn't being used for targeting, and it's generally better to minimize the use of id attributes unless necessary.
  6. Direct use of hx_post:

    • Instead of using a string "/increment" for hx_post, you're directly referencing the increment function. This is a very idiomatic FastHTML approach, as it allows for better refactoring and maintains the DRY (Don't Repeat Yourself) principle.

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.

@korakot
Copy link

korakot commented Sep 7, 2024

Amazing AI... and amazing way to teach AI using llms.txt too! Thanks Jeremy

@jph00
Copy link
Author

jph00 commented Sep 16, 2024

Yeah it's pretty cool! :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment