Skip to content

Routing

LoboGuardian 🐺 edited this page Jul 11, 2026 · 11 revisions

Routing

Server initialisation

The entry point of any HTeaLeaf app is the server object. HTeaLeaf supports three variants:

Variant Notes
ASGI Async, works with Uvicorn / Hypercorn
WSGI Sync, works with Gunicorn / uWSGI
CGI Legacy

ASGI (recommended)

from htealeaf import HteaLeaf, adapters
 
app = HteaLeaf(adapters.ASGI)
 

Run with any ASGI server, e.g. Uvicorn:

uvicorn myapp:app

WSGI

from htealeaf import HteaLeaf, adapters
 
app = HteaLeaf(adapters.WSGI)
 
 
if __name__ == "__main__":
    from wsgiref.simple_server import make_server
 
    with make_server("", 8000, app) as server:
        print("Serving on http://127.0.0.1:8000")
        try:
            server.serve_forever()
        except KeyboardInterrupt:
            print("\rBye")

Regardless of variant, the server object manages:

  • Route registry
  • Session management
  • Server-specific functionality (WSGI entrypoint, CGI handlers, etc.)

Registering routes

HTeaLeaf offers two equivalent ways to register routes.

Decorator style

@app.route("/hello")
def hello():
    return "Hello World!"

Imperative style

def hello():
    return "Hello World!"
 
app.add_path("/hello", hello)

Both approaches are equivalent. The decorator style is more concise for most cases; add_path is useful when routes are registered dynamically or assembled from a separate module.


Return values

Each route handler must return a valid HTTP response. Accepted types:

Type Description
str Plain text or HTML string
dict / list Serialised as JSON
bytes Raw byte response
HTML element Object built with htealeaf.elements
2-tuple (status, body) — e.g. return 401, "Unauthorized"
3-tuple Full HTTP response (status, headers, body)

⚠️ In the tuple forms the status comes first. return "Unauthorized", 401 would send "Unauthorized" as the status line and 401 as the body.

For redirects there is a ready-made helper:

from htealeaf.server.utils import redirect
 
@app.route("/old")
def old():
    return redirect("/new")

Async handlers

Route handlers can be async def — the server awaits them natively (sync handlers are run in an executor so they don't block the loop):

@app.route("/")
async def home(session, req: Request):
    ...

use_state() and @js work the same in both forms — the render context is propagated to the executor thread that runs sync handlers.


Path parameters

Dynamic segments are declared with curly braces {} and injected as function arguments:

@app.route("/hello/{name}")
def greet(name):
    return f"Hello, {name}!"

A request to /hello/John returns Hello, John!.


Accessing the request

Add a req: Request argument to receive the full request object:

from htealeaf.server.http import Request
 
@app.route("/echo")
def echo(req: Request):
    return {
        "method": req.method,
        "path": req.path,
        "body": req.text(),
        "json": req.json(),
    }

Other useful attributes: req.args (query-string dict), req.headers, req.form() (parsed form body, None if not a form), req.is_ssl.


Routes with session

Add a session argument to access the user's session:

from htealeaf.server import SessionData
 
@app.route("/profile")
def profile(session: SessionData):
    if session.has("userName"):
        return f"Welcome, {session['userName']}"
    return "Please log in"

HTeaLeaf injects arguments by parameter name (annotations are optional and ignored for matching): declare req, session, and/or cookies (a dict of the request's cookies) and only what you name gets passed.


Server hooks

The server exposes an event hook system — the same mechanism SuperStore uses to wire itself in. Register a callback with registry_hook:

from htealeaf.server.server import ServerEvent

def log_request(req):
    print(req.method, req.path)

app.registry_hook(ServerEvent.on_request, log_request)

Available events: on_request (receives the Request), on_response (status, body, headers — before rendering), on_render (the root Component about to be rendered), path_registered, and new_session.


Combining arguments

Path parameters, req, and session can be combined freely:

@app.route("/user/{id}/settings")
def user_settings(id, session: SessionData):
    if not session.has("userName"):
        return 401, "Unauthorized"
    return f"Settings for user {id}"

Next: Declarative HTML Elements

Clone this wiki locally