Hello world

One file, one route, one page.

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 hello
cd hello
uv add "stario>=5,<6"
uv add --dev watchfiles

2. 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, and asyncio.run(serve(bootstrap)) is the whole entry point. uvloop.run(serve(bootstrap)) is the uvloop version.

  • span in bootstrap is 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.