Getting started with Stario
Build one small app, run it locally, then add a long-lived stream only if you need live updates.
You need Python 3.14+ and a project tool. The steps below use uv.
1. Create a project
Pick one path:
Minimal project — an empty app plus Stario:
uv init --app hello-stariocd hello-stariouv add starioScaffolded template —
stario initoruvx stario@latest initgives a stock layout and a runnable demo (see Realtime tiles, especially Create the project). Use this when you want batteries included.
Both yield a normal Python project with Stario as a dependency.
2. First pass: static HTML
Replace main.py with:
from stario import ( App, Context, Span, Writer, html as h, responses,) async def home(c: Context, w: Writer) -> None: # c: request-side. w: response-side. responses.html( w, h.HtmlDocument( h.Head( h.Title("Hello from Stario"), ), h.Body( h.H1("Hello from Stario"), h.P("This is a normal HTML page."), ), ), ) async def bootstrap(app: App, span: Span) -> None: # This is the startup span. Add metadata to it. span.attr("app.name", "hello-stario") span.attr("app.version", "0.1.0") app.get("/", home, name="home")Shape of the app:
bootstrap(app, span)registers routes and shared setup.spancarries startup telemetry.app.get(...)binds a path to a handler.home(c, w)runs per request.responses.html(...)completes an HTML response.
3. Run the app
uv run stario watch main:bootstrapOpen http://127.0.0.1:8000 (default bind). You should see “Hello from Stario”.
4. Second pass: add realtime
When the same page should receive server-driven updates without navigation, Stario uses Datastar for a thin client and SSE from normal handlers.
Update main.py:
import asyncio from stario import ( App, Context, Span, Writer, datastar as ds, html as h, responses,) async def home(c: Context, w: Writer) -> None: responses.html( w, h.HtmlDocument( h.Head( h.Title("Hello from Stario"), ds.ModuleScript(), ), h.Body( # Start a long-lived connection to /ticks. ds.init(ds.get(c.app.url_for("ticks"), retry="always")), h.H1("Hello from Stario"), h.P("This page now receives updates from the server."), h.P({"id": "clock"}, "Waiting for ticks..."), ), ), ) async def tick_stream(): # Yield one new value every 0.5s. tick = 0 while True: tick += 1 yield tick await asyncio.sleep(0.5) async def ticks(c: Context, w: Writer) -> None: # All handlers can keep the connection as long as they want. c.span.event("stream.opened") async for tick in w.alive(tick_stream()): ds.sse.patch_elements(w, h.P({"id": "clock"}, f"Tick {tick}")) c.span.event("stream.closed") async def bootstrap(app: App, span: Span) -> None: # This is the startup span. Add metadata to it. span.attr("app.name", "hello-stario") span.attr("app.version", "0.1.0") app.get("/", home, name="home") app.get("/ticks", ticks, name="ticks")Reload the page. The paragraph with id clock should update about every 0.5s.
When you add POST actions that send Datastar signal JSON, validate that payload in your own helpers (how-to); the tick stream above is GET-only and does not need that step.
What changed:
ds.ModuleScript()loads Datastar in the page.ds.init(ds.get(...))opens a long-livedGETto/ticks.ticksis stillasync def handler(c, w); it stays open until the client or server ends the connection.w.alive(...)ties handler lifetime to the connection and shutdown.c.span.event(...)records stream open/close in telemetry.
5. Telemetry in the model
Operations matter as much as code: failures, latency, and deployment context should be observable without ad hoc logging everywhere. The tick handler above already records c.span.event on the stream—you are using the same primitives at a larger scale.
Startup and shutdown produce spans with server metadata.
spaninbootstrapis the startup span;c.spanis per request.Use
attr/attrsfor stable dimensions (version, environment).Use
eventfor discrete steps (stream opened, cache miss, …).
See Telemetry and Telemetry design.
6. Read next
Realtime tiles — shared state,
Relay, and SSE fan-out (flagship walkthrough).Create a project —
uvx stario init, templates, first run.Request and context —
c.req, body, query; Routing —Router,mount,name.The Stario way — design defaults in one place.
Datastar — attribute helpers, actions, and
stario.datastar.sse(patch/stream helpers).