FastHX and the case for server-side rendering in FastAPI
Declarative Python server-side rendering utility for FastAPI with built-in HTMX support.
At a glance
- What is it?
- FastHX is a Python library that puts a declarative decorator on top of FastAPI routes so they can return HTML as well as data, with HTMX as the intended consumer but no hard dependency on it. It keeps FastAPI's dependency injection, response headers and typing intact, which is exactly what a Jinja-plus-HTMLResponse setup tends to lose.
- Who is it for?
- FastHX is a good fit for a FastAPI team that wants HTMX-style interactivity while keeping route functions typed, injectable and testable, and the decorator approach costs less than it sounds. It is the wrong fit if you want a full component framework with its own router, since the README itself points at holm for a deeper FastAPI integration and a Next.js-like experience, or if your app is an API first with no HTML at all.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: FastAPI returns JSON, HTMX wants HTML
FastAPI is built around returning data, and it does that well. HTMX, on the other hand, expects a server that returns HTML fragments to swap into the page. Put the two together and you arrive at the usual pattern: a route returns data, a second route returns a template response, and the same logical operation exists twice.
FastHX is a library that closes that gap. The description calls it a Python server-side rendering utility for FastAPI with built-in HTMX support, and it is presented as a way to simplify building interactive web applications with FastAPI and hypermedia frameworks.
The framing matters for who should read this. The target user is a Python backend developer who has chosen FastAPI, likes its dependency injection, and wants server-rendered HTML without abandoning that. It is not a general template engine and it does not try to be one. The README also points at a separate project, holm, for an even deeper FastAPI integration and a Next.js-like developer experience, which tells you where the author thinks this library stops.
Decorators that leave your route function alone
The central design claim is that FastHX decorators always have access to the decorated route's result, all of its arguments, and the current request. Integrations use those values to build the data the rendering engine consumes, run the engine with the selected component, and return the result to the client.
What the decorators do not do is take over the route. The documentation stresses Python decorator syntax that works with FastAPI as one would expect, with no need for unused or magic dependencies in routes, and it lists that response headers set in routes are kept after rendering, as you would expect in FastAPI. Correct typing is called out too, so other typed decorators can still be applied to your routes.
That last point is the one that separates FastHX from a hand-rolled template helper. If your route signature stays intact, FastAPI keeps injecting dependencies into it, your test client still calls it, and your type checker still sees the return type. The library is built for both sync and async routes as well.
hx() versus page(): two decorators with different trigger rules
The rendering entry points differ in when they fire, and this is the part worth internalising.
With the Jinja integration, hx() only triggers HTML rendering for HTMX requests, while page() unconditionally renders HTML. The htmy integration exposes the same two decorator methods, hx() and page().
That distinction is what lets one route serve both audiences. The key feature list states that HTMX routes work as expected if they receive non-HTMX requests, so the same route can serve data and render HTML at the same time. A browser hitting the URL directly gets JSON; an HTMX request gets an HTML fragment.
Choosing between them is a question of intent. page() is for full pages, where the response is always HTML. hx() is for endpoints that are conceptually fragments or partial updates but still need to answer a plain GET sensibly. Using page() everywhere would mean a JSON API endpoint returns HTML to everyone, which is rarely what you want.
ComponentSelector: choosing what renders, including on errors
Rendering is not hardcoded to a single template. The ComponentSelector abstraction makes it possible to declaratively specify and dynamically select the component used to render the response to a given request.
That means the selector is a decision made per request rather than a fixed argument to the decorator. A route can inspect the request and pick a component accordingly.
The same abstraction covers the unhappy path. It is possible to define an error ComponentSelector that is used if the decorated route raises an exception, and the README names error rendering for incorrect user input as the typical use case. This is a structural advantage over the common FastAPI pattern of writing a try/except in every route that needs to render a friendly error page.
The rendering engine receives access to all dependencies of the decorated route, so a component can be given the same objects the route was working with. The documentation notes that the details of how data conversion works, and how it can be customised, live in the API documentation of the integration you choose.
Installing fasthx and wiring the htmy or jinja integration
The package is on PyPI and the install is a single line:
$ pip install fasthxThere are optional dependency extras for the two official integrations. The htmy extra pulls in htmy with lxml support, and the jinja extra pulls in jinja2 with an upper bound below version 4:
requires-python = ">=3.11"
dependencies = ["fastapi>=0.135.0"]The base install depends only on FastAPI itself, which is the point of the extras design. A project that only wants the core behaviour does not inherit a template engine it will not use.
Working with the htmy integration looks like this:
from fastapi import FastAPI
from pydantic import BaseModel
from fasthx.htmy import HTMYThe Jinja route is the same shape. You create a fasthx.jinja.Jinja instance from a FastAPI Jinja2Templates instance and use its hx() or page() methods as decorators. The documentation notes that FastHX works with any Python templating engine or server-side rendering library, for example htmy, jinja2 or dominate, but the two extras above are the official integrations.
A route that serves JSON and HTML from the same handler
The htmy example is compact enough to read in one pass. A Pydantic model describes the data, the app is created, an HTMY instance is created, and then the decorators are stacked under the FastAPI route decorators:
@app.get("/users")
@htmy.hx(UserList) # Render the result using the UserList component.
def get_users(rerenders: int = 0) -> list[User]:The route function returns a list of User objects and nothing about rendering leaks into it. The decorator above it decides how that list becomes a response.
Note the ordering, since it is the part people get wrong: the FastAPI route decorator goes on top, and the FastHX decorator sits between the route and the function. That is what keeps the endpoint registered with FastAPI while the FastHX layer handles the response transformation.
The Jinja example follows the same pattern with a template name instead of a component, and it shows an async route returning a list of models, which is where the sync and async support matters. A separate examples folder in the repository holds complete working versions, including htmy-rendering, htmy-streaming, jinja-rendering and a custom-rendering example.
Streaming, typing, and the maintenance picture
Async HTML streaming is a listed capability, aimed at time to first byte and first contentful paint, and there is a dedicated htmy-streaming example for it. That matters for pages that take a while to render, and it is the feature that pushes FastHX beyond the simplest Jinja wrapper.
The typing story is handled with care. The project runs mypy in strict mode, and the README makes a point of correct typing enabling other typed decorators on your routes. Development tooling in pyproject.toml includes ruff for linting and formatting, pytest with random-order enabled by default, and mkdocstrings for building the API reference.
On maintenance, the repository is not archived and the last push was on 2026-09-09. Recent releases are frequent: v3.2.2 on 2026-07-23, v3.2.1 on 2026-07-16 and v3.2.0 on 2026-07-06, a minor release cadence that suggests active work on a stable 3.x line. The licence is MIT, which removes any distribution question for a library you vendor into an application.
One dependency constraint is worth checking before you upgrade: the base requirement is fastapi>=0.135.0 and Python 3.11 or newer, so a project on an older FastAPI or Python has work to do first.
Editorial conclusion
FastHX is a good fit for a FastAPI team that wants HTMX-style interactivity while keeping route functions typed, injectable and testable, and the decorator approach costs less than it sounds. It is the wrong fit if you want a full component framework with its own router, since the README itself points at holm for a deeper FastAPI integration and a Next.js-like experience, or if your app is an API first with no HTML at all. Before adopting, try one route with hx() next to one plain JSON route to confirm the same handler can serve both, and check that your templating engine has a FastHX integration, because the built-in ones cover htmy and jinja only.
Frequently asked questions
How do I install FastHX and the htmy integration?
Install the base package with pip install fasthx, then add the htmy extra with pip install fasthx[htmy]. The jinja extra is pip install fasthx[jinja]. FastHX requires Python 3.11 or newer and fastapi>=0.135.0.
What is the difference between hx() and page() in FastHX?
The hx() decorator only triggers HTML rendering for HTMX requests, so the same route can serve JSON to a plain request and HTML to an HTMX one. The page() decorator unconditionally renders HTML, which suits full pages.
Can FastHX render error pages when a route raises an exception?
Yes. An error ComponentSelector can be defined and it is used when the decorated route raises an exception, with error rendering for incorrect user input given as the typical use case.
Does FastHX require HTMX to be used?
No. The README states it is built for HTMX but can be used without it, and it works with any Python templating engine or server-side rendering library, for example htmy, jinja2 or dominate.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/volfpeter-fasthx)Was this useful?