Hello world
Your first Stario app: a counter with Datastar client signals and one server route that patches them over SSE. Everything lives in a single main.py so you can see the full request path before realtime fan-out.
Stario is a Python 3.14+ framework for hypermedia web apps — explicit routes, server-rendered HTML, optional Datastar realtime, and built-in telemetry. Not ASGI: run with stario serve or stario watch and a bootstrap callable.
For where symbols live (stario vs stario.routing vs stario.http), see the Glossary.
Prerequisites
You need Python 3.14+ and a project tool. These docs use uv; install Python 3.14 with uv python install 3.14 if needed.
1. Run the example
Clone the stario repo (or copy examples/hello-world into your workspace):
git clone https://github.com/bobowski/stario.gitcd stario/examples/hello-worlduv syncuv run stario watch main:bootstrapOpen http://127.0.0.1:8000 (set STARIO_PORT if the default is busy). For a one-shot run: uv run stario serve main:bootstrap.
2. Play with it first
Before opening main.py:
Click − and + — the count changes in the browser (client signals on
$count).Click Server +1 — Python reads your signals, bumps the count, and pushes the update over SSE with
SSE(w).patch_signals.
That split — local reactivity plus a short server round-trip — is the smallest Datastar pattern. Getting started builds on it with Relay, SSE element patches, and POST commands.
3. URLs and assets
Routes and static files are declared as module-level data. AssetManifest fingerprints assets at import time; StaticAssets pays compression and caching cost once in bootstrap.
from pathlib import Pathfrom stario import AssetManifest, StaticAssets, UrlPath ASSETS = AssetManifest(Path(__file__).parent / "static")HOME = UrlPath("/")INCREMENT = UrlPath("/increment")One UrlPath constant per route — used in app.get and in views via .href(). No url_for.
4. Views
Pure functions build HTML as a tree of stario.markup.html callables. data.signals seeds client state; data.on wires clicks; data.text binds display text to a signal; at.get opens a GET to the server.
from stario.datastar import at, datafrom stario.markup import html as h def home_view(count: int): return h.Div( data.signals({"count": count}), h.Button(data.on("click", "$count--"), "-"), h.Div({"id": "count"}, data.text("$count")), h.Button(data.on("click", "$count++"), "+"), h.Button(data.on("click", at.get(INCREMENT.href())), "Server +1"), )responses.html(w, home_view(...)) sends the tree on first paint. Later, SSE(w).patch_signals(...) updates client state without replacing the whole page.
5. Handlers
Every handler has the same shape: async def handler(c: Context, w: Writer) -> None.
import stario.responses as responsesfrom stario.datastar import SSE, read_signals async def home(c, w): responses.html(w, home_view(count=0)) async def increment(c, w): signals = await read_signals(c.req) raw = signals.get("count", 0) try: n = int(raw) except TypeError, ValueError: n = 0 signals["count"] = n + 1 SSE(w).patch_signals(signals)read_signals expects a JSON object from Datastar on GET and POST requests that carry signals. Validation is plain Python — see Reading and writing signals for typed reads and error responses.
6. Bootstrap
Composition root: build long-lived objects, register static assets, register routes, then yield for shutdown.
from stario import App, Span async def bootstrap(app: App, span: Span): span.attr("static_dir", str(ASSETS.directory)) with span.step("static_assets") as s: static = StaticAssets(ASSETS) s.attrs(static.stats) static.register(app) app.get(HOME, home) app.get(INCREMENT, increment) yieldspan.attrs and span.step show up in startup telemetry (TTY, JSON, or SQLite) before any request is handled. See Telemetry.
7. Scaffold a real project
Single-file examples teach mechanics. New repos should scaffold app/features/ and app/common/ from day one — see AI-assisted development and Structuring apps. Copy AGENTS.md into the repo root for agent sessions.
uv init --app my-appcd my-appuv add "stario>=4,<5"curl -o AGENTS.md https://stario.dev/AGENTS.mduv run stario watch app.main:bootstrapRead next
Getting started — Tiles live demo with annotated source (Relay, SSE patches, 204 commands)
Reading and writing signals — validation patterns for signal payloads
Chat room — multi-file layout with SQLite and tests
The Stario way — design defaults in one place