FastAPI¶
PyJinHx integrates seamlessly with FastAPI for server-side rendered components.
Setup¶
Project Structure¶
my_app/
├── components/
│ └── ui/
│ ├── button.py
│ ├── button.html
│ ├── card.py
│ └── card.html
├── main.py
└── pyproject.toml
Basic Example¶
Component¶
# components/ui/button.py
from pyjinhx import BaseComponent
class Button(BaseComponent):
id: str
text: str
variant: str = "primary"
<!-- components/ui/button.html -->
<button id="{{ id }}" class="btn btn-{{ variant }}">
{{ text }}
</button>
App¶
# main.py
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from pyjinhx import Renderer
from components.ui.button import Button
Renderer.set_default_environment("./components")
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def index() -> str:
return f"""
<!DOCTYPE html>
<html>
<body>
<h1>Welcome</h1>
{Button(id="submit-btn", text="Submit", variant="primary").render()}
{Button(id="cancel-btn", text="Cancel", variant="secondary").render()}
</body>
</html>
"""
Using Jinja Base Templates¶
For larger applications, combine PyJinHx components with Jinja2 page templates:
from jinja2 import Environment, FileSystemLoader
from pyjinhx import Renderer
env = Environment(loader=FileSystemLoader("./templates"))
Renderer.set_default_environment(env)
@app.get("/", response_class=HTMLResponse)
def index():
template = env.get_template("index.html")
return template.render(
button=Button(id="main-btn", text="Click Me"),
)
<!-- templates/index.html -->
{% extends "base.html" %}
{% block content %}
<h1>My App</h1>
{{ button }}
{% endblock %}
Request-Scoped Registry¶
In web apps, component instances from one request can leak into the next. The recommended setup is a single call:
from fastapi import FastAPI
from pyjinhx import setup
app = FastAPI(lifespan=my_existing_lifespan) # optional — chained, not replaced
setup(app, context_factory=lambda req: AppLoadContext(db=get_db(req)))
setup(app, ...) chains your lifespan (if any), wires optional invalidation, and registers registry middleware with ClientBackend for header auto-resolution. By default (no invalidation_backend) the load cache is per-request — multi-worker safe; pass a backend to opt into cross-request caching. See Configuration and Reactivity → load() results are cached.
Per-Route (manual)¶
from pyjinhx import Registry
@app.get("/", response_class=HTMLResponse)
def index() -> str:
with Registry.request_scope():
return f"""
<!DOCTYPE html>
<html>
<body>
{Button(id="submit-btn", text="Submit").render()}
</body>
</html>
"""
Advanced: manual middleware¶
If you cannot use setup(app), define middleware yourself — see Client Backend and Registry guide.
Pair with @mutates on store methods so mutation routes can omit explicit dirtied
via setup() — see Reactivity
and Client Backend.
Reactive mutation routes:
@app.post("/rows/{todo_id}/toggle", response_class=HTMLResponse)
def toggle_row(todo_id: int) -> str:
store.toggle(todo_id)
return TodoItemRow.render(todo_id) # headers from ClientBackend
Tips¶
- Component assets: Components with adjacent
.jsand.cssfiles have their assets auto-injected. See Asset Collection. - Response types: FastAPI's
HTMLResponseworks directly withrender(), which returnsMarkupobjects that convert to strings automatically.