Session State

use_state gives each visitor a tiny amount of state that lives on the server for the life of their session. No database, no config.

The idea#

Koyo is static-first, but a visit counter or a theme toggle needs a little per-user memory. Sessions are the unit of that memory: every visitor gets their own isolated store that Koyo tracks with an httponly cookie. State is keyed per session, so two tabs from the same browser share a session while two different visitors never do.

The API#

use_state is a module-level function. Pass the request, a key, and an initial value. It returns a pair: a current value and an action object:

# components/counter.py
from koyoapp.html import button, div, p, span
from koyoapp.state import use_state

def visit_counter(request):
    count, count_action = use_state(request, "visit_count", 0)
    return div(class_="card", id="visit-card")[
        p("Session state, live right here"),
        div(class_="flex items-center gap-3")[
            span(id="visit-count")[f"Visits: {count}"],
            button(class_="btn", hx_target="#visit-card", hx_swap="outerHTML",
                   **count_action.increment(by=1))["+1"],
        ],
    ]

The action object changes the value through two helpers:

  • action.increment(by=1) adds to the current value.
  • action.set(value) replaces it.

Spraying the action onto the button (**action.increment(by=1)) fills in its hx_post URL automatically, and the hx_target plus hx_swap attributes swap in a fresh render of the card. A component like this lives in the scaffold's components/counter.py and is rendered by the home page.

How it works#

  • Mutations are delivered as POST requests to an internal Koyo route (/__koyo_state/<token>); you never build those URLs yourself.
  • The session cookie is named koyo_session, httponly, SameSite=Lax, and scoped to the site path.
  • Sessions expire after 30 minutes of inactivity and are recreated on the next visit.

Limitations#

State is in memory only. It is lost when the server restarts, it is not shared across processes or deployments, and it is not a database. Use it for a visit counter or a preference, not for carts, accounts, or anything that must survive a redeploy.

  • No disk or database backend ships with Koyo today.
  • A single server process holds all active sessions.
  • Two separate browser sessions do not share state.

State during the production build#

koyoapp build prerenders pages without a live request, so request is None and there is no session to read. use_state returns the initial value in that case. The result is safe static HTML: a static build always renders the defaults, never a private count.