Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Serving Dynamic Pages and Organizing Routes with Sanic Blueprints

Notes May 11 5

Sanic processes incoming HTTP requests by mapping Uniform Resource Identifiers (URIs) to asynchronous view functions. When a client connects, the framework matches the request path against registered routes, executes the corresponding handler, and returns an HTTP response object.

Routing Mechanics and View Handlers

The @app.route() decorator binds a specific URI pattern to an async callable. Inside the application lifecycle, this registration stores the handler reference alongside HTTP method constraints in an internal route table. Upon receiving a request, the server extracts the URL path, queries the route registry, invokes the matched function with the Request object as an argument, and passes the resulting response back to the underlying ASGI/HTTP server.

from sanic import Sanic
from sanic.response import text

app = Sanic("core_engine")

@app.route("/status")
async def check_health(request):
    return text({"status": "operational"})

In this structure, check_health acts as the bridge between network input and application logic. It decouples incoming traffic from business processing, allowing clean separation of concerns.

Project Organization Conventions

Maintaining a growing number of endpoints requires deliberate directory structuring. Standard practices include:

  • Placing static assets (CSS, JavaScript, images) under a dedicated assets or static root.
  • Storing template files in a templates directory.
  • Grouping route handlers within a routes or controllers package.

As applications scale, flat routing configurations become difficult to manage. Conflicts arise when multiple developers register overlapping paths, and static/template file distribution becomes chaotic.

Modular Routing with Blueprints

Blueprints solve scalability issues by encapsulating routes, template directories, and static asset mappings into reusable modules. Each blueprint operates with an isolated namespace, supports custom URL prefixes, and reduces naming collisions across large codebases.

Consider a service split into two functional areas: a document viewer for rendered HTML and a data exchange layer returning JSON payloads. Using Blueprints, these sections can be developed independently while sharing a single entry point.

Recommended Directory Layout

project_root/
├── app_entry.py
├── assets/
│   ├── doc_viewer/
│   │   ├── css/
│   │   └── js/
│   └── data_portal/
│       ├── css/
│       └── js/
├── templates/
│   ├── doc_viewer/
│   │   └── base.html
│   └── data_portal/
│       └── manifest.html
└── routes/
    ├── __init__.py
    ├── html_router.py
    └── json_router.py

HTML Viewer Blueprint

This module handles template rendering using Jinja2 with asynchronous support.

# routes/html_router.py
import sys
from jinja2 import Environment, PackageLoader, select_autoescape
from sanic import Blueprint
from sanic.response import html

enable_async = sys.version_info >= (3, 6)

doc_bp = Blueprint("document_view", url_prefix="/docs")

# Configure static file serving
pkg_name = "routes"
pkg_path = doc_blueprint.static(
    "/docs/static",
    "./assets/doc_viewer"
)

env = Environment(
    loader=PackageLoader(pkg_name, "../templates/doc_viewer"),
    autoescape=select_autoescape(["html", "xml"]),
    enable_async=enable_async
)

async def render_page(tpl_name, context=None):
    ctx = context or {}
    tmpl = env.get_template(tpl_name)
    return html(await tmpl.render_async(**ctx))

@doc_bp.route("/")
async def show_dashboard(request):
    return await render_page("base.html", {"section": "main"})

@doc_bp.route("/archive")
async def list_documents(request):
    sample_docs = [
        {"id": "A1", "title": "System Overview", "href": "#/view/A1"}
    ]
    return await render_page("base.html", {"items": sample_docs})

JSON Data Blueprint

This module exposes structured data without template rendering.

# routes/json_router.py
from sanic import Blueprint
from sanic.response import json

data_bp = Blueprint("data_exchange", url_prefix="/api")

@data_bp.route("/feed/entries")
async def get_feed_items(request):
    raw_records = [
        {
            "title_detail": {"value": "Scaling Asynchronous Workloads"},
            "link": "https://example.com/scale"
        }
    ]
    transformed_payload = [
        {"headline": rec["title_detail"]["value"], "target_url": rec["link"]}
        for rec in raw_records
    ]
    return json(transformed_payload)

Route Initialization

Aggregate all blueprints in the application factory or startup script to ensure they are registered before the server binds to ports.

# routes/__init__.py
from .html_router import doc_bp
from .json_router import data_bp
# app_entry.py
import os
from sanic import Sanic
from src.routes import doc_bp, data_bp

app = Sanic("unified_gateway")

app.register_blueprint(doc_bp)
app.register_blueprint(data_bp)

# Serve global static files
app.static("/resources", os.path.join(os.getcwd(), "assets"))

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080, debug=True)

Using this modular approach eliminates prefix duplication and isolates resource dependencies. Each blueprint maintains its own template roots and static directories, preventing cross-contamination during development. Testing the implementation involves targeting the prefixed endpoints dircetly:

GET http://localhost:8080/docs/
GET http://localhost:8080/docs/archive
GET http://localhost:8080/api/feed/entries

Accessing these endpoints verifies that each blueprint operates independently while sharing the same application instance.

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.