You will write one file and run it. At the end you will have a Stario app with one page, and you will know what each line does.
1. Make a project#
You need Python 3.12 or newer and uv. Install has more options.
uv init --app hellocd hellouv add "stario>=5,<6"uv add --dev watchfiles2. Write main.py#
Replace the generated main.py with this file.
import asyncio from stario import App, Context, Route, Span, Writer, h, serve HOME = Route("GET /") def home_view(): return h.HtmlDocument( {"lang": "en"}, h.Head(h.Title("Hello, Stario")), h.Body( h.H1("Hello, Stario"), h.P("This page is one handler and one route."), ), ) async def home(c: Context, w: Writer) -> None: w.html(home_view()) async def bootstrap(app: App, span: Span): app.add(HOME, home) yield if __name__ == "__main__": asyncio.run(serve(bootstrap))3. Run it#
uv run watchfiles 'python main.py' .Open http://127.0.0.1:8000. You should see the heading and one sentence. If the port is busy, put STARIO_PORT=8080 in front of the command.
watchfiles restarts the server when a file changes. Edit the sentence in home_view, save, and reload the browser. uv run python main.py runs the same app without the watcher.
The terminal also shows the first request: the route, the status, and how long it took. That is Stario’s telemetry. It is on by default.
4. What each part does#
The route. Route("GET /") is one HTTP method on one path. It is a value. You create it once at import, and you use the same object to register the handler and to build links: HOME.href() is "/".
The view. home_view returns an HTML tree. Tags are functions in stario.markup.html. A dict becomes attributes. Other arguments become children. The view knows nothing about HTTP.
The handler. home is async def handler(c, w). c is the request. w is the response. w.html(...) sets the status and content type, writes the body, and finishes the response. Every handler in Stario has this shape.
The bootstrap. bootstrap assembles the app. It is an async generator with one yield. Everything before the yield runs at startup. Everything after it runs at shutdown. Here it registers one route.
5. Make it yours#
Add a second page. Create ABOUT = Route("GET /about"), write a handler for it, register it in bootstrap, and link to it from the home page with h.A({"href": ABOUT.href()}, "About"). The watcher picks up the change.
By the way#
Stario is not ASGI, so you do not run it under Uvicorn.
serve()is the server, andasyncio.run(serve(bootstrap))is the whole entry point.uvloop.run(serve(bootstrap))is the uvloop version.spaninbootstrapis the startup span.span.attr("app.name", "hello")adds a line to the startup trace. You do not need it yet.A version of this file lives in the Stario repository as
examples/hello-world/main.py, with a few extra lines.
Next: Realtime tiles keeps one response open and pushes HTML to every open tab.