Starlette & FastAPI Integration

Use Kida with Starlette and FastAPI via kida.contrib.starlette

5 min read 901 words

Use Kida with Starlette and FastAPI throughkida.contrib.starlette. The integration provides KidaTemplates -- a drop-in replacement for Starlette's Jinja2Templatesthat supports context processors, HTMX metadata, and async rendering.

Installation

pip install starlette kida
# or for FastAPI:
pip install fastapi uvicorn kida

Starlette Setup

from starlette.applications import Starlette
from starlette.requests import Request
from starlette.routing import Route
from kida.contrib.starlette import KidaTemplates

templates = KidaTemplates(directory="templates")

async def homepage(request: Request):
    return templates.TemplateResponse(
        request, "home.html", {"title": "Home"}
    )

app = Starlette(routes=[
    Route("/", homepage),
])

The KidaTemplatesconstructor accepts:

Parameter Description
directory Path to template directory
env Pre-configured KidaEnvironment (use instead of directory)
context_processors List of callables that take a request and return a dict
**env_kwargs Extra keyword arguments passed toEnvironment()

You must provide eitherdirectory or env, but not both.

Using a Pre-configured Environment

from kida import Environment, FileSystemLoader

env = Environment(
    loader=FileSystemLoader("templates"),
    autoescape=True,
)

# Register custom filters/globals on env first
@env.filter()
def currency(value):
    return f"${value:,.2f}"

templates = KidaTemplates(env=env)

Context Processors

Context processors run on everyTemplateResponseand inject additional variables into the template context:

def user_context(request):
    return {"current_user": request.state.user}

def site_context(request):
    return {"site_name": "My App"}

templates = KidaTemplates(
    directory="templates",
    context_processors=[user_context, site_context],
)

Every template rendered through TemplateResponse will have access to current_user and site_namewithout passing them explicitly.

FastAPI Setup

The sameKidaTemplatesclass works with FastAPI:

from fastapi import FastAPI, Request
from kida.contrib.starlette import KidaTemplates

app = FastAPI()
templates = KidaTemplates(directory="templates")

@app.get("/")
async def homepage(request: Request):
    return templates.TemplateResponse(
        request, "home.html", {"title": "Home"}
    )

@app.get("/users/{username}")
async def user_profile(request: Request, username: str):
    user = await get_user(username)
    return templates.TemplateResponse(
        request, "profile.html", {"user": user}
    )

TemplateResponseParameters

Parameter Default Description
request (required) Starlette/FastAPIRequestobject
name (required) Template name to render
context None Dict of template variables
status_code 200 HTTP response status code
headers None Additional response headers
media_type None Response media type

Therequest object is automatically added to the template context, so you can access it in templates as {{ request }}.

Streaming Responses

For large pages or real-time content, use Kida's async streaming with Starlette'sStreamingResponse:

from starlette.responses import StreamingResponse

@app.get("/feed")
async def feed(request: Request):
    template = templates.get_template("feed.html")

    async def generate():
        async for chunk in template.render_stream_async(
            items=await get_items(),
            request=request,
        ):
            yield chunk

    return StreamingResponse(generate(), media_type="text/html")

Templates can use {% flush %}to control chunk boundaries, sending content to the client as soon as key sections are ready.

Block Rendering for HTMX

TheKidaTemplatesintegration automatically detects HTMX requests and sets RenderContext metadata. When an HTMX request comes in, the following metadata keys are set:

  • hx_request -- True if HX-Requestheader is present
  • hx_target -- Value of HX-Targetheader
  • hx_trigger -- Value of HX-Triggerheader
  • hx_boosted -- True if HX-Boosted header is "true"

Combine this withrender_block()to return only the part of the page that HTMX needs:

from fastapi.responses import HTMLResponse

@app.get("/items")
async def items_list(request: Request):
    items = await get_items()
    template = templates.get_template("items.html")

    # If HTMX request, render just the items block
    if request.headers.get("HX-Request"):
        html = template.render_block("items_list", items=items)
    else:
        html = template.render(items=items, request=request)

    return HTMLResponse(html)
{# templates/items.html #}
{% extends "base.html" %}

{% block content %}
    <h1>Items</h1>
    {% block items_list %}
    <ul id="items">
        {% for item in items %}
            <li>{{ item.name }}</li>
        {% end %}
    </ul>
    {% end %}
{% end %}

For async streaming of a single block:

from starlette.responses import StreamingResponse

@app.get("/items")
async def items_list(request: Request):
    template = templates.get_template("items.html")

    async def generate():
        async for chunk in template.render_block_stream_async(
            "items_list", items=await get_items()
        ):
            yield chunk

    return StreamingResponse(generate(), media_type="text/html")

Async Templates

Kida supports native async constructs in templates. Userender_stream_async() for templates that contain {% async for %} or {{ await }}expressions:

@app.get("/dashboard")
async def dashboard(request: Request):
    template = templates.get_template("dashboard.html")

    # For templates with async constructs, use render_stream_async
    chunks = []
    async for chunk in template.render_stream_async(request=request):
        chunks.append(chunk)

    return HTMLResponse("".join(chunks))

For templates without async constructs, render_async()runs the synchronous render in a thread pool so it won't block the event loop:

@app.get("/about")
async def about(request: Request):
    template = templates.get_template("about.html")
    html = await template.render_async(title="About", request=request)
    return HTMLResponse(html)

Complete Example

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from kida import Environment, FileSystemLoader
from kida.contrib.starlette import KidaTemplates

# Configure environment
env = Environment(
    loader=FileSystemLoader("templates"),
    autoescape=True,
)

@env.filter()
def format_datetime(value, fmt="%Y-%m-%d"):
    return value.strftime(fmt)

# Context processor
def common_context(request):
    return {"site_name": "My App"}

# Set up templates
app = FastAPI()
templates = KidaTemplates(env=env, context_processors=[common_context])

@app.get("/", response_class=HTMLResponse)
async def homepage(request: Request):
    return templates.TemplateResponse(
        request, "home.html", {"title": "Home"}
    )

@app.get("/items", response_class=HTMLResponse)
async def items_list(request: Request):
    items = await get_items()
    template = templates.get_template("items.html")

    if request.headers.get("HX-Request"):
        html = template.render_block("items_list", items=items)
        return HTMLResponse(html)

    return templates.TemplateResponse(
        request, "items.html", {"items": items}
    )
{# templates/home.html #}
{% extends "base.html" %}

{% block title %}{{ title }}{% end %}

{% block content %}
    <h1>{{ title }}</h1>
    <p>Welcome to {{ site_name }}!</p>
{% end %}

See Also