Hello world

Your first Stario app: one route, one handler, one HTML page. Everything lives in a single main.py. The goal is to see how a Stario process starts and how a request becomes a document — before Datastar, Relay, or a project tree.

Stario is a Python 3.14+ framework for hypermedia web apps. It is not ASGI. You run a bootstrap callable with stario serve or stario watch.

For where symbols live (stario 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):

bash
git clone https://github.com/bobowski/stario.git
cd stario/examples/hello-world
uv sync
uv run stario watch main:bootstrap

Open 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.

You should see a heading and one sentence. That is the whole app.

2. The route

A Route is one HTTP method on one path. You declare it once, register it, and build links from the same object. There is no url_for.

python
from stario import Route
 
HOME = Route("GET /")

When one path has more than one method, compose a string and pin each Route. Realtime tiles and Go-to architecture show that split.

3. The view and the handler

A view is a pure function that returns an HTML tree. Tags live in stario.markup.html.

python
from stario.markup import html as h
 
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."),
        ),
    )

Every handler has the same shape: async def handler(c: Context, w: Writer) -> None. The handler does not return a response object. It writes through Writer.

python
import stario.responses as responses
from stario import Context, Writer
 
async def home(c: Context, w: Writer) -> None:
    responses.html(w, home_view())

c is the request. w is the response. responses.html sets status, content type, and body, then finishes the writer. See Handlers and the writer.

4. Bootstrap

bootstrap is the composition root. It is an async generator with exactly one yield. Work before yield is startup. Work after yield is teardown.

python
from stario import App, Span
 
async def bootstrap(app: App, span: Span):
    span.attr("app.name", "hello-world")
    app.add(HOME, home)
    yield

app.add takes a Route and a handler. span.attr shows up in startup telemetry (TTY, JSON, or SQLite) before any request is handled. See Telemetry.

This example has no static files and no Datastar. That is intentional.

5. 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.

bash
uv init --app my-app
cd my-app
uv add "stario>=4,<5"
curl -o AGENTS.md https://stario.dev/AGENTS.md
uv run stario watch app.main:bootstrap