Skip to content

Instantly share code, notes, and snippets.

@jph00
Last active December 9, 2024 20:44
Show Gist options
  • Save jph00/42cbf4889081fedfedee5f9a15374ace to your computer and use it in GitHub Desktop.
Save jph00/42cbf4889081fedfedee5f9a15374ace to your computer and use it in GitHub Desktop.

Building Hypermedia Apps with FastHTML

πŸ§‘ human (Sep 5, 2024, 02:05 PM)

How can FastHTML help me write Hypermedia applications?

πŸ€– assistant (Sep 5, 2024, 02:05 PM)

FastHTML is designed to help you write hypermedia applications efficiently by providing a framework that aligns closely with hypermedia principles. Here are some key ways FastHTML can assist:

  1. Simplified routing and handlers: FastHTML allows you to create routes using concise decorators:

    @rt("/")
    async def get():
        return Titled("Home", P("Welcome to my app"))

    This approach keeps your code clean and focused on the hypermedia interactions.

  2. Built-in HTMX support: FastHTML integrates seamlessly with HTMX, allowing you to create interactive applications without heavy JavaScript:

    @rt("/contacts")
    async def get():
        contacts = Contact.all()
        return Ul(*[Li(c.name, hx_get=f"/contacts/{c.id}", hx_target="#details") for c in contacts])
  3. FT (FastTags) for HTML generation: FastHTML provides a Pythonic way to generate HTML, making it easy to create dynamic content:

    return Div(
        H1("Contacts"),
        Form(
            Input(type="text", name="name", placeholder="Enter name"),
            Button("Add", hx_post="/contacts", hx_target="#contact-list")
        ),
        Div(id="contact-list")
    )
  4. Automatic full page responses: When a titled response is requested, FastHTML automatically wraps it in a full HTML page structure:

    return Titled("Contact List", contact_list_content)
  5. Session handling and authentication: FastHTML includes built-in support for sessions and authentication, which is crucial for many hypermedia applications:

    @rt("/protected")
    async def get(auth):
        if not auth:
            return RedirectResponse("/login")
        return Titled("Protected Page", P(f"Welcome, {auth}!"))
  6. Database integration: FastHTML works well with various databases, including SQLite through the FastLite library:

    from fasthtml.common import *
    db = database('data/app.db')
    contacts = db.t.contacts
    
    @rt("/")
    async def get():
        return Titled("Contacts", Ul(*[Li(c.name) for c in contacts.all()]))
  7. Easy partial updates: FastHTML makes it simple to return partial content for HTMX requests, enabling smooth updates without full page reloads:

    @rt("/search")
    async def get(q: str):
        results = contacts.search(name=q)
        return Ul(*[Li(c.name) for c in results])
  8. Built-in Beforeware: FastHTML's Beforeware allows you to run code before route handlers, useful for things like authentication checks:

    def auth_check(req, sess):
        if not sess.get('user'):
            return RedirectResponse('/login')
    
    app = FastHTML(before=Beforeware(auth_check, skip=['/login', '/static/*']))

By leveraging these features, FastHTML enables you to create hypermedia applications that are interactive, efficient, and adhere to hypermedia principles, all while keeping your code clean and Pythonic.

πŸ§‘ human (Sep 5, 2024, 02:06 PM)

I don't really understand what hypermedia applications are. Can you explain, with FastHTML examples?

πŸ€– assistant (Sep 5, 2024, 02:06 PM)

Certainly! Let me explain hypermedia applications and provide some FastHTML examples to illustrate the concept.

Hypermedia applications are web applications that follow the principles of hypermedia, which is an extension of hypertext. In hypermedia applications:

  1. The server sends HTML responses containing both data and controls (like links and forms).
  2. The client (usually a web browser) uses these controls to navigate and interact with the application.
  3. The application state is driven by the hypermedia controls provided in each response.

Key characteristics of hypermedia applications include:

  1. Server-driven UI: The server decides what controls to send to the client.
  2. Statelessness: Each request contains all necessary information.
  3. Progressive enhancement: Basic functionality works without JavaScript, with interactivity added on top.

Now, let's look at some FastHTML examples to illustrate these concepts:

  1. Basic Hypermedia Navigation:
from fasthtml.common import *

app, rt = fast_app()

@rt("/")
async def get():
    return Titled("Home", 
        H1("Welcome to My Hypermedia App"),
        P("Choose a section:"),
        Ul(
            Li(A("About", href="/about")),
            Li(A("Contact", href="/contact"))
        )
    )

@rt("/about")
async def get():
    return Titled("About", 
        H1("About Us"),
        P("We are a hypermedia-driven company."),
        A("Back to Home", href="/")
    )

@rt("/contact")
async def get():
    return Titled("Contact", 
        H1("Contact Us"),
        Form(
            Input(type="email", name="email", placeholder="Your email"),
            Textarea(name="message", placeholder="Your message"),
            Button("Send", type="submit"),
            hx_post="/submit-contact",
            hx_target="#result"
        ),
        Div(id="result")
    )

@rt("/submit-contact")
async def post(email: str, message: str):
    # Process the contact form submission
    return P("Thank you for your message!")

serve()

In this example:

  • The home page provides links to navigate the application.
  • The about page displays information and a link back to home.
  • The contact page includes a form, demonstrating hypermedia controls for user input.
  1. Dynamic Content Loading:
from fasthtml.common import *

app, rt = fast_app()

@rt("/")
async def get():
    return Titled("Product Catalog",
        H1("Our Products"),
        Div(
            Button("Load Products", 
                   hx_get="/products", 
                   hx_target="#product-list",
                   hx_trigger="click"),
            Div(id="product-list")
        )
    )

@rt("/products")
async def get():
    products = [
        {"name": "Widget A", "price": 19.99},
        {"name": "Gadget B", "price": 24.99},
        {"name": "Doohickey C", "price": 15.99}
    ]
    return Ul(*[
        Li(f"{p['name']} - ${p['price']}", 
           hx_get=f"/product-details/{i}", 
           hx_target="#product-details")
        for i, p in enumerate(products)
    ])

@rt("/product-details/{id}")
async def get(id: int):
    products = [
        {"name": "Widget A", "price": 19.99, "description": "A fantastic widget"},
        {"name": "Gadget B", "price": 24.99, "description": "An amazing gadget"},
        {"name": "Doohickey C", "price": 15.99, "description": "A superb doohickey"}
    ]
    product = products[id]
    return Div(
        H2(product["name"]),
        P(f"Price: ${product['price']}"),
        P(product["description"]),
        Button("Add to Cart", 
               hx_post=f"/add-to-cart/{id}",
               hx_target="#cart-status")
    )

@rt("/add-to-cart/{id}")
async def post(id: int):
    # Logic to add item to cart would go here
    return P("Item added to cart!")

serve()

In this example:

  • The initial page load is minimal, with a button to load products.
  • Clicking the button uses HTMX to load and display the product list without a full page reload.
  • Each product in the list can be clicked to load its details, again without a full page reload.
  • The "Add to Cart" button demonstrates how actions can be performed and feedback given, all within the hypermedia paradigm.

These examples demonstrate how hypermedia applications can provide rich, interactive experiences while maintaining the simplicity and robustness of traditional web architectures. FastHTML makes it easy to create such applications by providing a clean, Pythonic way to generate HTML responses with built-in HTMX support.

πŸ§‘ human (Sep 5, 2024, 02:06 PM)

Why not just use a JSON API?

πŸ€– assistant (Sep 5, 2024, 02:06 PM)

Great question. While JSON APIs are popular, hypermedia-driven applications using HTML responses (as facilitated by FastHTML) offer several advantages:

  1. Simplicity and reduced complexity: With FastHTML, you can create interactive applications without building separate frontend and backend systems. For example:

    @rt("/contacts")
    async def get(search: str = ''):
        contacts = Contact.search(name=search)
        return Ul(*[
            Li(c.name, 
               hx_get=f"/contacts/{c.id}", 
               hx_target="#details")
            for c in contacts
        ])

    This single route handles both data retrieval and presentation.

  2. Progressive enhancement: Basic functionality works without JavaScript, improving accessibility and robustness:

    @rt("/")
    async def get():
        return Titled("Contacts", 
            Form(
                Input(name="search", placeholder="Search contacts"),
                Button("Search"),
                action="/contacts",
                method="get"
            ),
            Div(id="results")
        )

    This form works with or without JavaScript.

  3. Reduced client-side logic: The server determines the application flow, simplifying the client:

    @rt("/contacts/{id}")
    async def get(id: int):
        contact = Contact.get(id)
        return Div(
            H2(contact.name),
            P(contact.email),
            Button("Edit", 
                   hx_get=f"/contacts/{id}/edit",
                   hx_target="#contact-form")
        ) if contact else P("Contact not found")

    The server decides what actions are available for each contact.

  4. Avoiding over-fetching: You can send exactly the HTML needed, rather than sending all data and rendering client-side:

    @rt("/dashboard")
    async def get():
        return Grid(
            Div(hx_get="/user-summary", hx_trigger="load"),
            Div(hx_get="/recent-activity", hx_trigger="load"),
            Div(hx_get="/notifications", hx_trigger="load")
        )

    Each component loads independently, potentially improving perceived performance.

  5. Easier caching: HTML responses can be cached more easily than JSON data that requires client-side rendering:

    @rt("/product/{id}")
    async def get(id: int):
        product = Product.get(id)
        return Div(
            H1(product.name),
            P(product.description),
            P(f"Price: ${product.price}"),
            hx_push_url="true"
        )

    This HTML can be cached and served directly by a CDN.

  6. Reduced version coupling: Changes to the backend don't necessarily require frontend updates:

    @rt("/user-profile")
    async def get(user: User):
        return Form(
            Input(name="name", value=user.name),
            Input(name="email", value=user.email),
            # New field added server-side, old clients still work
            Input(name="phone", value=user.phone),
            Button("Update", hx_post="/user-profile")
        )

    Adding new fields doesn't break existing clients.

  7. Better SEO: Search engines can easily index content that's sent as HTML:

    @rt("/blog/{slug}")
    async def get(slug: str):
        post = BlogPost.get(slug)
        return Article(
            H1(post.title),
            P(post.content),
            Meta(name="description", content=post.summary)
        )

    This content is immediately accessible to search engines.

While JSON APIs have their place, especially for mobile apps or complex SPAs, hypermedia applications can often provide a simpler, more maintainable solution for many web applications. FastHTML makes it easy to build such applications efficiently.

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