Routing

Keywords: dynamic urls, routing params, index files, pure html, custom error pages, page.py, static files, url parameters, index.py

Asok uses a file-based router. The directory structure of src/pages/ defines your URLs.

Basic routing

URL

File path

/

src/pages/page.py (or page.html)

/about

src/pages/about/page.py

/contact

src/pages/contact/page.py

/blog

src/pages/blog/page.py

Index files

By default, Asok looks for a file named page.py or page.html inside a folder.

  • src/pages/page.py → /

  • src/pages/auth/login/page.py → /auth/login

You can change this default name in your configuration: app.config['INDEX'] = "index" would look for index.py.

Dynamic parameters

To capture variables from the URL, use square brackets in the folder name.

URL

Folder path

Param

/user/42

src/pages/user/[id]/page.py

request.params['id'] == "42"

/blog/my-post

src/pages/blog/[slug]/page.py

request.params['slug'] == "my-post"

Parameter types

You can optionally specify a type for automatic validation and conversion:

  • [id:int] — matches digits only, request.params['id'] is a Python int

  • [name:str] — matches any string (default)

  • [uid:uuid] — matches a UUID string

  • [path:path] — matches multiple segments (e.g. /docs/[path:path] matches /docs/api/routing)

Example: src/pages/product/[id:int]/page.py If a user visits /product/abc, Asok returns a 404 Not Found because “abc” is not an integer.

Request methods

Every page can handle different HTTP methods by defining get(), post(), put(), delete(), etc.

# src/pages/contact/page.py

def get(request):
    return request.html('contact.html')

def post(request):
    # Process form...
    request.flash('success', 'Thank you!')
    return request.redirect('/contact')

If a specific method is not defined, Asok falls back to the render() function. If neither is defined, it returns 405 Method Not Allowed.

Pure HTML pages

For simple static pages, you don’t even need a .py file. Just create a .html file:

src/pages/about/page.html → serves this HTML at /about.

Static files

Files in src/partials/ are served at /static/.

File path

URL

src/partials/css/base.css

/css/base.css

src/partials/js/base.js

/js/base.js

src/partials/images/logo.svg

/images/logo.svg

Use static() in templates:

<link rel="stylesheet" href="{{ static('css/base.css') }}">
<img src="{{ static('images/logo.svg') }}">

Custom error pages

Create a page for any HTTP error code:

  • src/pages/404/page.html: Custom 404 page

  • src/pages/403/page.html: Custom 403 page (triggered by CSRF failures or security protection)

  • src/pages/500/page.html: Custom 500 page

  • src/pages/429/page.html: Custom 429 page (Rate limiting)

<!-- src/pages/404/page.html -->
{% extends "html/base.html" %}

{% block main %}
<h1>Page not found</h1>
<p>Sorry, this page doesn't exist.</p>
<a href="/">Go home</a>
{% endblock %}

In production (DEBUG=false), the 500 page is shown instead of the raw error.

Client-Side Single Page Application (SPA) Router

In addition to file-based backend routing, Asok provides a native declarative SPA router (asok-router) with deterministic transitions, dynamic parameters, and zero-reload navigation.

Basic SPA Router Setup

Wrap your page views inside a container marked with asok-router, and define routes using <template asok-route="/path">:

<main asok-router asok-transition="slide-left 200">
    <!-- Home Route -->
    <template asok-route="/" asok-meta:title="Home - Asok App">
        <div class="p-8">
            <h1 class="text-3xl font-bold">Welcome Home</h1>
            <a asok-link="/contact" class="text-blue-600">Contact page →</a>
        </div>
    </template>

    <!-- Contact Route -->
    <template asok-route="/contact" asok-meta:title="Contact Us">
        <div class="p-8" asok-state="{ name: '' }">
            <h1 class="text-3xl font-bold">Contact Us</h1>
            <form asok-fetch-post="/api/contact" asok-reset class="mt-4">
                <input asok-model="name" type="text" name="name" required>
                <button type="submit">Send</button>
            </form>
        </div>
    </template>
</main>

Programmatic Navigation

Navigate dynamically from JavaScript:

// Navigate to a route
window.AsokRouter.navigate('/contact');

// Replace history entry
window.AsokRouter.navigate('/dashboard', true);

Listening to Route Changes (onRouteChange)

Execute custom logic, track analytics, or update component states whenever the active route changes using onRouteChange (or its alias afterEach):

// Listen to all route changes
window.AsokRouter.onRouteChange((route) => {
    console.log("New route:", route.path);
    console.log("Parameters:", route.params);
    console.log("Query parameters:", route.query);
    console.log("Hash:", route.hash);

    if (route.path === '/contact') {
        // Specific logic for contact page!
        console.log("Initializing contact form");
    } else if (route.path === '/about') {
        // Specific logic for about page!
        console.log("Loaded About page");
    }
});

Note: The callback passed to onRouteChange is executed immediately upon registration with the current active route, and then automatically every time the route changes (either via window.AsokRouter.navigate() or browser back/forward buttons).

Native DOM Event (asok:routechange)

Asok also dispatches a standard CustomEvent on window whenever navigation occurs:

window.addEventListener('asok:routechange', (event) => {
    console.log("New route:", event.detail.path);
    console.log("Parameters:", event.detail.params);
    console.log("Query:", event.detail.query);
    console.log("Hash:", event.detail.hash);
});

Inspecting Current Route State

You can read the current route details synchronously at any point:

// Active route state object (reactive)
console.log(window.AsokRouter.route.path);   // e.g. "/contact"
console.log(window.AsokRouter.route.params); // e.g. { id: "42" }
console.log(window.AsokRouter.route.query);  // e.g. { tab: "messages" }
console.log(window.AsokRouter.route.hash);   // e.g. "#section-2"

// Direct property getters are also available
console.log(window.AsokRouter.path);
console.log(window.AsokRouter.params);
console.log(window.AsokRouter.query);
console.log(window.AsokRouter.hash);

Dynamic Route Parameters (:param)

Define dynamic segments in client-side templates using :paramName:

<main asok-router>
    <!-- Dynamic profile route -->
    <template asok-route="/user/:id" asok-meta:title="User Profile">
        <div class="p-8">
            <h1 class="text-2xl font-bold">User Profile</h1>
            <p>User ID: <span asok-text="window.AsokRouter.params.id"></span></p>
        </div>
    </template>
</main>

When navigating to /user/123:

  • window.AsokRouter.route.params contains { id: '123' }.

  • window.AsokRouter.route.path is "/user/123".

Route Pattern Matching (matchPattern)

Test whether a URL matches a route pattern and extract its parameters:

const params = window.AsokRouter.matchPattern('/user/:id', '/user/42');
// params => { id: '42' }

const mismatch = window.AsokRouter.matchPattern('/about', '/contact');
// mismatch => null

Manual Router Update (update)

If you dynamically inject new route templates or modify DOM nodes at runtime, trigger a router update:

window.AsokRouter.update();