"""
example_app
~~~~~~~~~~~
Validation, JWT authentication, SSE, and frontend serving with Wred.
Install the example dependencies and run from the repository root::
python -m pip install -e '.[server]' PyJWT msgspec sse-starlette servestatic
export JWT_SECRET="$(python -c 'import secrets; print(secrets.token_hex(32))')"
mkdir -p frontend
echo '<h1>Wred example</h1>' > frontend/index.html
weft example_app:app
ServeStatic serves frontend/index.html at http://127.0.0.1:8000/.
Replace the placeholder HTML with your frontend build. Existing files take
priority over Wred routes; keep greet, me, and events out of that directory.
POST /greet accepts a JSON object with a name of 1–40 characters.
GET /me requires a Bearer token signed with JWT_SECRET using HS256,
with issuer "demo", audience "greetings", a nonempty subject, and an
expiration time.
GET /events streams five messages using sse-starlette. Watch them arrive::
curl -N http://127.0.0.1:8000/events
EventSourceResponse is returned directly from a Wred handler, while
ServeStatic wraps the app. If you use a reverse proxy, configure it to pass
SSE events through without buffering.
:copyright: (c) 2026 by Harrison Erd.
:license: BSD-3-Clause, see LICENSE for more details.
"""
import asyncio
import os
from pathlib import Path
from typing import Annotated
import jwt
import msgspec
from servestatic import ServeStaticASGI
from sse_starlette import EventSourceResponse
from wred import App
SECRET = os.environ["JWT_SECRET"]
api = App()
class Greeting(msgspec.Struct, forbid_unknown_fields=True):
name: Annotated[str, msgspec.Meta(min_length=1, max_length=40)]
def verified_user(authorization):
scheme, _, token = (authorization or "").partition(" ")
if scheme.lower() != "bearer" or not token:
raise jwt.InvalidTokenError("Bearer token required")
claims = jwt.decode(
token,
SECRET,
algorithms=["HS256"],
issuer="demo",
audience="greetings",
options={"require": ["sub", "exp", "iss", "aud"]},
)
if not claims["sub"]:
raise jwt.InvalidTokenError("User required")
return claims["sub"]
@api.route("/greet", methods=["POST"])
async def greet(request):
try:
data = msgspec.json.decode(await request.body(), type=Greeting)
except msgspec.DecodeError:
return (
{"detail": "Invalid greeting"},
[("Content-Type", "application/json")],
422,
)
return {"message": f"Hello, {data.name}!"}
@api.route("/me")
async def me(request):
try:
user = verified_user(request.headers.get("authorization"))
except jwt.InvalidTokenError:
return (
{"detail": "Not authenticated"},
[
("Content-Type", "application/json"),
("WWW-Authenticate", "Bearer"),
],
401,
)
return {"user": user}
@api.route("/events")
async def events(request):
async def messages():
for number in range(5):
yield {"data": f"Event {number}"}
await asyncio.sleep(1)
return EventSourceResponse(messages())
app = ServeStaticASGI(
api,
root=Path(__file__).resolve().parent / "frontend",
index_file=True,
)