Custom applications
This guide demonstrates how to use a custom FastAPI application and fasthx.htmy.HTMY
instance (HTML renderer) in your holm
application. This allows you to leverage holm
's features while keeping full control over all underlying software components.
In the vast majority of applications, using a default FastAPI application (one created by simply calling FastAPI()
, as holm
internally does) is not enough. Exception handlers, for example, can be registered with holm
, but you often need to set up a lifespan method, register middlewares, or customize the OpenAPI documentation to your needs. As a matter of fact, you could even add routes to your application manually, just make sure their paths don't conflict with the routes holm
registers for you automatically!
When it comes to HTML rendering, you may occasionally need to configure the fasthx.htmy.HTMY
instance with custom request processors, formatters, translation utilities, or global rendering context.
Doing all of this in holm
is very straightforward, all you need to do is:
- Create a
FastAPI
instance somewhere in your codebase and configure it, just like you would do in any FastAPI project. - Create a
fasthx.htmy.HTMY
instance and configure it. - Pass the created instances to the
holm.App()
function asApp(app=my_fastapi_instance, htmy=my_htmy_instance)
.
The below example demonstrates these steps as simply as possible, through creating:
- a
FastAPI
instance with a/health-check
route, and - a
fasthx.htmy.HTMY
instance with a request processor.
If you followed the quick-start-guide, then you can make these changes in the main.py
file and immediately see the result in action.
from fastapi import FastAPI
from fasthx.htmy import HTMY
from holm import App
app = FastAPI()
htmy = HTMY(
request_processors=[
# Add a request processor that inserts an "is_htmx_request" key
# into htmy rendering contexts, making this information available
# to htmy components as `context["is_htmx_request"]`.
lambda request: {"is_htmx_request": request.headers.get("HX-Request") == "true"}
]
)
@app.get("/health-check")
def health_check() -> dict[str, str]:
"""Health check route registered directly on the FastAPI application."""
return {"status": "ok"}
# If holm.App() receives a FastAPI application instance, then it does its job and
# returns the same object. This means you don't need to keep a separate reference
# to the returned value, the already existing app variable is enough.
App(app=app, htmy=htmy)
That's it! If you start the application and open its OpenAPI documentation (http://localhost:8000/docs
) in your browser, you will see the health check route you registered, in addition to all the routes holm
discovered and registered automatically for you.
Note: If you return a htmy
component from a manually registered route, it will not be rendered as HTML automatically! The reason for this is simple: holm
must not modify the behavior of user-defined application parts in any way. You can still do HTML rendering manually using the htmy
instance in the same way as in holm
API modules (see the Rendering APIs with HTMX guide).