Flaxon 0.3.0 and Teloce-Py 0.3.0 are available on PyPI. Release instructions are maintained on this site; source links identify the framework snapshot. Check version and upgrade guidance.

Framework source · Markdown

Growing a Flaxon application

This guide is for an application with roughly 200 pages, endpoints, views, or feature screens. At that size, the important decision is not the number of files—it is keeping each feature independent enough to change and test safely.

Use feature modules

Keep each bounded feature together: its routes, schemas, service, templates, and tests. Do not put business logic in route functions or create one global routes.py file.

myapp/
├── app/
│   ├── main.py                 # application factory and global middleware
│   ├── config.py               # environment-specific settings
│   ├── modules/
│   │   ├── accounts/
│   │   │   ├── routes.py
│   │   │   ├── schemas.py
│   │   │   ├── service.py
│   │   │   └── templates/accounts/
│   │   ├── catalog/
│   │   │   ├── routes.py
│   │   │   ├── schemas.py
│   │   │   ├── service.py
│   │   │   └── templates/catalog/
│   │   └── billing/
│   │       ├── routes.py
│   │       ├── schemas.py
│   │       └── service.py
│   └── infrastructure/
│       ├── database.py
│       ├── cache.py
│       └── logging.py
├── tests/
│   ├── modules/
│   ├── integration/
│   └── security/
└── pyproject.toml

This layout works just as well for 20 features as it does for 200 pages. Split a large feature into submodules only when that feature itself becomes hard to navigate.

Use the same boundary for Pydantic and FastMCP

Optional integrations belong inside the feature module that uses them. The application factory should mount modules and provide shared dependencies; it should not contain every schema or tool.

# app/modules/catalog/module.py
from typing import Any

from pydantic import BaseModel

from flaxon.modules import FlaxonModule

catalog = FlaxonModule("catalog")


class CreateProduct(BaseModel):
    name: str
    price: float


@catalog.post("/")
async def create_product(product: CreateProduct) -> CreateProduct:
    return product


def install_catalog(app: Any) -> None:
    app.mount_module(catalog, prefix="/api/products")
# app/modules/ai_tools/module.py
from typing import Any

from fastmcp import FastMCP

from flaxon.integrations.fastmcp import mount_fastmcp

mcp = FastMCP("Store Tools")


@mcp.tool
async def find_product(name: str) -> dict[str, str | bool]:
    return {"name": name, "available": True}


def install_ai_tools(app: Any, auth_provider: Any) -> None:
    mount_fastmcp(
        app,
        mcp,
        path="/mcp",
        auth=auth_provider,
        require_auth=True,
        stateless_http=True,
    )

Mount both from one factory:

# app/main.py
from flaxon import Flaxon

from app.auth import auth_provider
from app.modules.ai_tools.module import install_ai_tools
from app.modules.catalog.module import install_catalog


def create_app() -> Flaxon:
    app = Flaxon("store", debug=False)
    install_catalog(app)
    install_ai_tools(app, auth_provider)
    return app


app = create_app()

Install the optional dependencies explicitly:

python -m pip install "flaxon[pydantic,mcp]"
flaxon run app.main:app --reload

The resulting contracts are easy to find: normal JSON clients call POST /api/products/, while MCP clients connect to /mcp. Pydantic validates the JSON body; FastMCP handles MCP protocol messages; the feature service is where shared business rules belong. Do not call mcp.run() when it is mounted into Flaxon.

Keep the application entry point small

The application entry point should compose feature routers and global infrastructure; it should not contain product rules.

# app/main.py
from flaxon import Flaxon
from flaxon.middleware import CORSMiddleware, RequestIDMiddleware, SecurityHeadersMiddleware

from app.modules.accounts.routes import router as accounts_router
from app.modules.catalog.routes import router as catalog_router


def create_app() -> Flaxon:
    app = Flaxon("myapp", debug=False)
    app.add_middleware(RequestIDMiddleware)
    app.add_middleware(SecurityHeadersMiddleware)
    app.add_middleware(
        CORSMiddleware,
        allowed_origins=["https://app.example.com"],
        allow_credentials=True,
    )
    app.include_router(accounts_router)
    app.include_router(catalog_router)
    return app


app = create_app()

Use explicit origins when credentials are enabled. Flaxon rejects the unsafe combination of allow_credentials=True and allowed_origins=["*"].

Put HTTP code in routes and product rules in services

Routes convert HTTP input into a service call. Services should not depend on Request, Response, or template objects, which keeps the same rule usable from HTTP, WebSocket, task, and CLI entry points.

# app/modules/catalog/routes.py
from flaxon import Router
from flaxon.validation import Schema, fields

from .service import CatalogService

router = Router(prefix="/api/v1/products")
service = CatalogService()


class CreateProduct(Schema):
    name = fields.StrField(required=True, min_length=1, max_length=200)
    price_cents = fields.IntField(required=True, minimum=0)


@router.post("")
async def create_product(data: CreateProduct) -> dict:
    product = await service.create(data.to_dict())
    return {"product": product}
# app/modules/catalog/service.py
class CatalogService:
    async def create(self, values: dict) -> dict:
        # Call a repository or an external API here.
        return {"id": "new-product", **values}

Draw clear ownership lines

Concern Put it in Keep it out of
Path, method, status code, headers route service/repository
Input shape and validation schema template/business service
Product rules and workflows service route handler
Persistence queries repository/infrastructure route handler
Cross-cutting HTTP behavior middleware individual routes
HTML presentation Jinax templates service

Add capabilities deliberately

At this scale, use framework features where they match the boundary:

  • Middleware: request IDs, security headers, CORS, body limits, trusted hosts, compression, logging, and timeouts.
  • Validation: typed JSON payloads with Schema and the *Field classes.
  • Jinax: server-rendered pages and reusable template layouts.
  • WebSockets: real-time connections and in-process room broadcasts.
  • Tasks: work that must not hold an HTTP request open.
  • Caching: cache read-heavy, invalidation-safe data with cached_async.
  • Observability: the built-in /health, /health/live, /health/ready, and /metrics endpoints, plus your own dependency checks.
  • OpenAPI and GraphQL: enable only when the project actually exposes those interfaces.

Plan for more than one worker

An in-memory object belongs to one Python process. That includes the default session backend, task queue, cache, rate-limit state, and WebSocket room manager. A multi-worker or multi-instance deployment must not rely on those objects for shared state.

For a horizontally scaled application:

  1. Keep route handlers stateless.
  2. Store authoritative data in your database or object storage.
  3. Use a shared service such as Redis when sessions, cached values, task coordination, or WebSocket fan-out must cross process boundaries.
  4. Use the Redis-backed adapters only after installing and operating Redis; they are not part of the core install.
  5. Run background workers separately from HTTP workers.

Keep templates manageable

For 200 server-rendered pages, use a base template, feature-owned template folders, and view models prepared by services/routes. Do not place database queries, permission decisions, or network calls inside Jinax templates.

Copy-paste feature module with Jinax

Router keeps a feature's endpoints together, while Jinax renders its templates through the shared application engine:

# app/main.py
from flaxon import Flaxon
from flaxon.jinax import Jinax
from app.modules.catalog.routes import router as catalog_router

app = Flaxon("catalog", debug=True)
app.use_templates(Jinax("app/templates", auto_reload=True))
app.include_router(catalog_router, prefix="/store")
# app/modules/catalog/routes.py
from flaxon import Router

router = Router(prefix="/catalog")

@router.get("")
async def catalog(request):
    return await request.render("catalog/index.html", {
        "title": "Catalog",
        "products": [{"name": "Starter", "price": 19}],
    })

This serves GET /store/catalog. Create app/templates/catalog/index.html:

<!doctype html>
<html><head><title>{{ title }}</title></head>
<body><h1>{{ title }}</h1>
{% for product in products %}<article>{{ product.name }}: ${{ product.price }}</article>{% endfor %}
</body></html>

Run pip install "flaxon[templates]" and flaxon run app.main:app --reload.

Test by feature and by boundary

  • Unit-test services without an ASGI client.
  • Use TestClient for routes, middleware, validation, and error responses.
  • Use AsyncWebSocketClient for WebSocket protocol tests; use AsyncTestClient when the HTTP test itself runs inside an async test.
  • Add integration tests for the real database/cache adapters you choose.
  • Keep regression tests beside the feature that previously failed.

Release checklist for a large application

  • Production configuration has DEBUG=False and a non-default secret.
  • CORS origins and trusted hosts are explicit.
  • Database migrations, backups, and restore drills exist.
  • In-memory defaults are not used as cross-worker shared state.
  • Health checks cover critical dependencies.
  • Metrics, structured logs, alerts, and request IDs are connected to your operations tooling.
  • Load tests use the real ASGI server, deployment topology, and dependencies.

Use this capability in a full-stack app

Flaxon serves Python APIs and Teloce interfaces in one project. Keep validation, authorization and privileged operations on the server; components call those endpoints and present their results. Follow the full-stack lessons and Teloce integration reference to connect this capability to pages, components and application modules.