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.
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
Schemaand the*Fieldclasses. - 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/metricsendpoints, 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:
- Keep route handlers stateless.
- Store authoritative data in your database or object storage.
- Use a shared service such as Redis when sessions, cached values, task coordination, or WebSocket fan-out must cross process boundaries.
- Use the Redis-backed adapters only after installing and operating Redis; they are not part of the core install.
- 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
TestClientfor routes, middleware, validation, and error responses. - Use
AsyncWebSocketClientfor WebSocket protocol tests; useAsyncTestClientwhen 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=Falseand 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.