Skip to content

Existing / plain FastAPI + HedronRouter

Use Hedron’s routing and HTML responses without the Hedron() facade when you already own a FastAPI app. You must install session and security middleware yourself.

Prefer hedron new for the first-hour Refresh demo. This page is the existing-app path.

FastAPI pin — Supported vs declared

For a known-good first mount, use FastAPI >=0.121.0,<0.142 (CI-supported). Declared metadata allows up to <0.150, but versions outside Supported are not CI-proven. Shared or older FastAPI environments often fail to resolve — use a clean venv. See troubleshooting and Compatibility.

Minimal include:

from fastapi import FastAPI
from hedron import HedronRouter, Page, Text, mount_hedron_static

api = FastAPI()
mount_hedron_static(api)
ui = HedronRouter(prefix="/ui")


@ui.page("/")
def home() -> Page:
    return Page(Text("Hello from Hedron"), title="Home")


api.include_router(ui)

Full listing with CSRF and sessions:

app.py
import secrets

from fastapi import FastAPI, Form, Request
from starlette.middleware.sessions import SessionMiddleware

from hedron import HedronRouter, Page, SubmitButton, Text, TextInput, html, mount_hedron_static
from hedron.security import (
    SecurityHeadersMiddleware,
    SecurityPolicy,
    csrf_token_for_request,
)
from hedron.security.csrf import ensure_csrf_cookie

api = FastAPI(title="Existing API")
policy = SecurityPolicy.from_name("standard")
api.state.hedron_security = policy
# Required for handle ownership on refresh/patch responses (same as Hedron()).
api.state.hedron_app_id = secrets.token_hex(8)

api.add_middleware(SessionMiddleware, secret_key="replace-in-production")
api.add_middleware(SecurityHeadersMiddleware, policy=policy)
mount_hedron_static(api)

ui = HedronRouter(prefix="/ui")


@ui.page("/")
def home(request: Request) -> Page:
    token = csrf_token_for_request(request, policy)
    page = Page(
        html.form(
            html.input(type="hidden", name="csrf_token", value=token),
            TextInput("note", value=""),
            SubmitButton("Save"),
            action="/ui/save",
            method="post",
        ),
        title="UI",
    )
    return page


@ui.action("/save", method="POST")
def save(note: str = Form(...)) -> Page:
    return Page(Text(f"Saved: {note}"), title="Saved")


api.include_router(ui)


@api.middleware("http")
async def seed_csrf_cookie(request: Request, call_next):
    response = await call_next(request)
    if request.method.upper() in {"GET", "HEAD", "OPTIONS"}:
        token = csrf_token_for_request(request, policy)
        ensure_csrf_cookie(response, policy, token=token, request=request)
    return response

HedronRouter still validates CSRF on unsafe page/component/action methods when app.state.hedron_security is set. The middleware above seeds the cookie on safe GETs the way Hedron() does.

Errors you must handle yourself

Situation Behavior on this path
CSRF missing/invalid on unsafe method HTTP 403 from Hedron route CSRF when policy is set
FastAPI outside >=0.121.0,<0.142 Install / resolver failure — clean venv or pin FastAPI
Missing session middleware Session/CSRF features break — install SessionMiddleware
Production without build manifest Prefer Hedron(production=True) path or run hedron build yourself

What you still configure

Concern Responsibility
Session middleware SessionMiddleware with a real secret
Security headers SecurityHeadersMiddleware
Security policy on app.state Set hedron_security
Handle ownership id on app.state Set hedron_app_id (auto-minted on first HedronRoute response if omitted)
CSRF cookie seeding Safe-GET middleware or equivalent (see above)
Static HTMX / assets mount_hedron_static / build asset mounts
Explorer Mount hedron-explorer only if you need it
Production build hedron build + deploy manifest.json when using production gates

For most new apps, prefer Hedron() (API). Use this HedronRouter + include_router path when integrating into an existing FastAPI service. Mounting a full Hedron() sub-app with api.mount(...) is an alternate — see Mount. The reference app demonstrates both styles.

See also