Content hash: 86cabb3908e77f25ffc5840db32e0dc799478c66425517e8740c45cfe6fcb068
#!/usr/bin/env python3
"""HTMX + Alpine.js frontend server — no build step, no npm.
Serves a search page with HTMX partial swaps and Alpine client state.
Run: python server.py → http://localhost:8080
Requires: pip install fastapi uvicorn jinja2
"""
from __future__ import annotations
from fastapi import FastAPI, Query, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory=".")
# ── Mock data ───────────────────────────────────────────────────────────
ITEMS = [
"Python programming",
"JavaScript basics",
"Docker containers",
"PostgreSQL queries",
"Redis caching",
"HTMX tutorials",
"Alpine.js reactivity",
"REST API design",
"GraphQL queries",
"WebSocket connections",
]
# ── Routes ──────────────────────────────────────────────────────────────
@app.get("/", response_class=HTMLResponse)
def index(request: Request):
return templates.TemplateResponse(
request=request, name="index.html", context={"items": ITEMS}
)
@app.get("/search", response_class=HTMLResponse)
def search(request: Request, q: str = Query(default="", max_length=100)):
is_htmx = request.headers.get("HX-Request") == "true"
if not q.strip():
results = ITEMS
else:
results = [item for item in ITEMS if q.lower() in item.lower()]
template = "results.html" if is_htmx else "index.html"
return templates.TemplateResponse(
request=request, name=template, context={"items": results, "query": q}
)
# ── Template files (write on first run) ────────────────────────────────
INDEX_HTML = r"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTMX + Alpine Demo</title>
<script src="https://unpkg.com/htmx.org@1.9.12" defer></script>
<script src="https://unpkg.com/alpinejs@3.14.1" defer></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 min-h-screen p-8" x-data="{ dark: false }">
<div class="max-w-xl mx-auto">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">Search Demo</h1>
<button @click="dark = !dark"
:class="dark ? 'bg-gray-700 text-white' : 'bg-gray-200 text-gray-700'"
class="px-3 py-1 rounded text-sm">Toggle Dark</button>
</div>
<!-- Search form: HTMX-driven, triggers on keyup -->
<input type="search" name="q" placeholder="Type to search..."
hx-get="/search" hx-trigger="keyup changed delay:200ms"
hx-target="#results" hx-swap="innerHTML"
class="w-full px-4 py-3 border border-gray-300 rounded-lg mb-4
focus:outline-none focus:ring-2 focus:ring-blue-500">
<!-- HTMX swap target -->
<div id="results">
{% include 'results.html' %}
</div>
</div>
</body>
</html>
"""
RESULTS_HTML = r"""<ul class="space-y-2">
{% if query %}
<p class="text-sm text-gray-500 mb-2">{{ items|length }} result(s) for "{{ query }}"</p>
{% endif %}
{% for item in items %}
<li class="p-3 bg-white rounded shadow-sm border border-gray-200
hover:border-blue-400 transition-colors">
{{ item }}
</li>
{% else %}
<li class="p-4 text-gray-500 text-center">No results found.</li>
{% endfor %}
</ul>
"""
if __name__ == "__main__":
import os, sys
# Write templates
for name, content in [("index.html", INDEX_HTML), ("results.html", RESULTS_HTML)]:
with open(name, "w") as f:
f.write(content)
print(f"Wrote {name}")
# Only start server if --serve flag passed
if "--serve" in sys.argv:
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
else:
print("Templates written. Run with --serve to start the dev server.")