Chat room

Multi-room chat with SQLite. The larger-app step after Realtime tiles. Source: examples/chat-room.

You should already know the tiles loop (Relay, c.alive, CQRS-shaped routes). Hello world is optional — it is only bootstrap and one HTML page. This page is the behaviour of the chat-room tree. File roles live in Structuring apps.

1. Run it

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

Open http://127.0.0.1:8000 (set STARIO_PORT if the default is busy). Tests: uv run pytest.

2. What this example adds

ConcernTilesChat room
LayoutOne fileFeatures under app/features/
StateIn-memory GameSQLite via app/db.py
ConfigConstants in fileConfig.from_env()
TestsNone in exampletests/test_lobby.py, tests/test_room.py

The HTTP pattern is the same as tiles: GET paints HTML once, GET /subscribe stays open with SSE(w).patch_elements, and POST commands reply 204, then work, then publish through Relay. Lobby create / delete redirect.

3. Start at bootstrap

app/main.py reads config, opens Database and Relay, registers static assets, then calls each register_*.

python
async def bootstrap(app: App, span: Span):
    config = Config.from_env()
    db = Database(config.db_path)
    db.apply_schema(room_data.SCHEMA)
    relay = Relay()
    await ASSETS.attach(app)
    register_lobby(app, db, relay)
    register_room(app, db, relay)
    yield

Nothing in app/features/ imports bootstrap.

4. Routes and jobs

RouteJob
GET /Lobby — list rooms and online counts
GET /subscribeLobby SSE — patch the room list on relay events
POST /roomsCreate room from dialog signals
DELETE /rooms/{id}Delete room and related rows
GET /rooms/{id}First paint; mint demo user identity
GET /rooms/{id}/subscribeLong-lived SSE; patch HTML on relay events
POST /rooms/{id}/send204 first, then store and publish
POST /rooms/{id}/typing204 first, then typing flag and publish

Commands read signals via read_*_signals and validate in plain Python. Subscribe handlers open async with relay.subscribe(...) as live: and loop with async for subject, _ in c.alive(live):. When a room is deleted under open tabs, the handler calls SSE(w).navigate(LOBBY.href()).

Room endpoints are Route constants in app/features/room/urls.py. Handlers register app.add(ROOM, …). Views call SEND.href(room_id=room.id) or at.post(SEND.href(room.id)).

5. Configuration and tests

CHAT_DB_PATH defaults to :memory:. Stario does not load dotenv files. Server listen address and tracers use STARIO_* (Configuration).

Tests use async with TestClient(app.main.bootstrap) — the same bootstrap the CLI loads. Testing with TestClient.

Structuring apps. Injecting dependencies.