For Python fans, the documentation is here: https://asok-framework.github.io/asok-docs
Asok Logo Asok
esc

Type to search across all documentation

5 min read
Edit on GitHub

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>

Use asok-link on links or buttons for seamless client-side SPA navigation without full page reloads.

Specify the target URL using the standard href attribute and attach asok-link:

<nav>
    <a href="/" asok-link asok-active-class="font-bold underline">Home</a>
    <a href="/contact" asok-link asok-active-class="font-bold underline">Contact</a>
</nav>

Compact syntax with asok-link="/path":#

You can also declare the path directly within asok-link. Asok automatically populates the href attribute on the <a> element:

<nav>
    <a asok-link="/" asok-active-class="font-bold underline">Home</a>
    <a asok-link="/contact" asok-active-class="font-bold underline">Contact</a>
</nav>

Active Classes (asok-active-class)#

The asok-active-class attribute specifies the CSS class(es) to add automatically when the current URL matches the link: - Default class: 'active' if asok-active-class is omitted. - Multiple space-separated classes supported: You can provide multiple classes (e.g. Tailwind utility classes: asok-active-class="font-bold underline text-blue-600"). Asok automatically splits and applies each class individually to classList without throwing token syntax errors.

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();