Routing

In Koyo the file tree is the route table. No central router file to edit, the path to a page.py decides the URL.

Path conventions#

File URL
app/page.py /
app/about/page.py /about
app/blog/[slug]/page.py /blog/:slug

The scaffold ships only the first row: a single home page with a call to action linking to the Koyo documentation. The other rows are what you add yourself: folders inside app/ become path segments, and a page.py inside any folder becomes the handler for that path.

The page() function#

Each page module exports a page() function that returns HTML:

# app/post/page.py
from koyoapp.html import div, h1, p

def page():
    return div(class_="max-w-2xl mx-auto py-12")[
        h1("About Koyo"),
        p("A static-first web framework."),
    ]

The return value can be an element built from the koyoapp.html tag factories or a plain string. For static routes page() takes no arguments.

Dynamic segments#

A folder whose name is wrapped in brackets becomes a parameter. The segment value is passed to page() as a keyword argument:

# app/blog/[slug]/page.py
from koyoapp.html import article, h1

def page(slug):
    return article(class_="prose")[
        h1("Post " + slug),
    ]

Add a file at app/blog/[slug]/page.py and it renders URLs such as /blog/hello-world.

HTTP methods#

Every route accepts the standard methods: GET, HEAD, POST, PUT, PATCH, and DELETE. The session counter on the scaffold home page posts through use_state's internal route and swaps in a fresh render, see session state.

Missing pages#

When no route matches, Koyo returns a 404 page. A file in public/ is served before falling back to routing, so static files take priority for their paths.

During development with KOYO_DEV=1 set, unhandled errors show a debug page with a traceback. Outside of development, Koyo returns a generic internal server error and keeps the traceback in the dev server log.

Internal routes#

Koyo reserves the /__koyo_state/... namespace for session state updates. You do not need to create or manage these routes; they are handled for you when a page uses use_state. The vendored htmx library is served at /htmx.min.js.