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
git clone https://github.com/bobowski/stario.gitcd stario/examples/chat-roomuv syncuv run stario watch app.main:bootstrapOpen http://127.0.0.1:8000 (set STARIO_PORT if the default is busy). Tests: uv run pytest.
2. What this example adds
| Concern | Tiles | Chat room |
|---|---|---|
| Layout | One file | Features under app/features/ |
| State | In-memory Game | SQLite via app/db.py |
| Config | Constants in file | Config.from_env() |
| Tests | None in example | tests/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_*.
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) yieldNothing in app/features/ imports bootstrap.
4. Routes and jobs
| Route | Job |
|---|---|
GET / | Lobby — list rooms and online counts |
GET /subscribe | Lobby SSE — patch the room list on relay events |
POST /rooms | Create room from dialog signals |
DELETE /rooms/{id} | Delete room and related rows |
GET /rooms/{id} | First paint; mint demo user identity |
GET /rooms/{id}/subscribe | Long-lived SSE; patch HTML on relay events |
POST /rooms/{id}/send | 204 first, then store and publish |
POST /rooms/{id}/typing | 204 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.