Large Application Example

A structured large application with routers, services, repositories, schemas, and middleware.

Project Structure

myapp/
├── app.py
├── config.py
├── models/
│   └── user.py
├── schemas/
│   └── user.py
├── services/
│   └── user_service.py
├── repositories/
│   └── user_repository.py
├── routes/
│   ├── __init__.py
│   ├── users.py
│   └── auth.py
├── middleware/
│   └── auth.py
└── utils/
    └── db.py

Configuration

# config.py
import os
from typing import Any

class Config:
    DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
    SECRET_KEY = os.environ.get("SECRET_KEY", "change-me")
    DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///app.db")
    ALLOWED_ORIGINS = os.environ.get("ALLOWED_ORIGINS", "http://localhost:3000").split(",")
    JWT_EXPIRATION = int(os.environ.get("JWT_EXPIRATION", 3600))
    MAX_BODY_SIZE = int(os.environ.get("MAX_BODY_SIZE", 10 * 1024 * 1024))

    @classmethod
    def to_dict(cls) -> dict[str, Any]:
        return {
            "DEBUG": cls.DEBUG,
            "SECRET_KEY": cls.SECRET_KEY,
            "DATABASE_URL": cls.DATABASE_URL,
            "ALLOWED_ORIGINS": cls.ALLOWED_ORIGINS,
            "JWT_EXPIRATION": cls.JWT_EXPIRATION,
            "MAX_BODY_SIZE": cls.MAX_BODY_SIZE,
        }

Models

# models/user.py
from dataclasses import dataclass
from datetime import datetime
from typing import Optional

@dataclass
class User:
    id: Optional[int] = None
    name: str = ""
    email: str = ""
    hashed_password: str = ""
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "name": self.name,
            "email": self.email,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "User":
        return cls(
            id=data.get("id"),
            name=data.get("name", ""),
            email=data.get("email", ""),
            hashed_password=data.get("hashed_password", ""),
            created_at=data.get("created_at"),
            updated_at=data.get("updated_at"),
        )

Schemas (Validation)

# schemas/user.py
from flaxon.validation import Schema, fields

class CreateUser(Schema):
    name = fields.String(required=True, min_length=2, max_length=80)
    email = fields.Email(required=True)
    password = fields.String(required=True, min_length=8)

class UpdateUser(Schema):
    name = fields.String(required=False, min_length=2, max_length=80)
    email = fields.Email(required=False)

class LoginRequest(Schema):
    email = fields.Email(required=True)
    password = fields.String(required=True, min_length=8)

Repository

# repositories/user_repository.py
from typing import Optional, List
from models.user import User

class UserRepository:
    def __init__(self, db):
        self.db = db

    async def get_by_id(self, user_id: int) -> Optional[User]:
        row = await self.db.fetch_one("SELECT * FROM users WHERE id = $1", user_id)
        return User.from_dict(row) if row else None

    async def get_by_email(self, email: str) -> Optional[User]:
        row = await self.db.fetch_one("SELECT * FROM users WHERE email = $1", email)
        return User.from_dict(row) if row else None

    async def create(self, user: User) -> User:
        row = await self.db.fetch_one(
            "INSERT INTO users (name, email, hashed_password) VALUES ($1, $2, $3) RETURNING *",
            user.name, user.email, user.hashed_password
        )
        return User.from_dict(row)

    async def update(self, user: User) -> User:
        row = await self.db.fetch_one(
            "UPDATE users SET name = $1, email = $2, updated_at = NOW() WHERE id = $3 RETURNING *",
            user.name, user.email, user.id
        )
        return User.from_dict(row)

    async def delete(self, user_id: int) -> bool:
        await self.db.execute("DELETE FROM users WHERE id = $1", user_id)
        return True

    async def list(self, limit: int = 100, offset: int = 0) -> List[User]:
        rows = await self.db.fetch_all("SELECT * FROM users LIMIT $1 OFFSET $2", limit, offset)
        return [User.from_dict(row) for row in rows]

    async def count(self) -> int:
        return await self.db.fetch_val("SELECT COUNT(*) FROM users")

Service Layer

# services/user_service.py
from typing import Optional, List
from flaxon.security import hash_password, verify_password
from models.user import User
from repositories.user_repository import UserRepository

class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo

    async def get_user(self, user_id: int) -> Optional[User]:
        return await self.repo.get_by_id(user_id)

    async def get_user_by_email(self, email: str) -> Optional[User]:
        return await self.repo.get_by_email(email)

    async def create_user(self, name: str, email: str, password: str) -> User:
        if await self.repo.get_by_email(email):
            raise ValueError("Email already registered")

        user = User(
            name=name,
            email=email,
            hashed_password=hash_password(password),
        )
        return await self.repo.create(user)

    async def authenticate(self, email: str, password: str) -> Optional[User]:
        user = await self.repo.get_by_email(email)
        if not user:
            return None
        if not verify_password(password, user.hashed_password):
            return None
        return user

    async def update_user(self, user_id: int, data: dict) -> Optional[User]:
        user = await self.repo.get_by_id(user_id)
        if not user:
            return None
        if "name" in data:
            user.name = data["name"]
        if "email" in data:
            user.email = data["email"]
        return await self.repo.update(user)

    async def delete_user(self, user_id: int) -> bool:
        return await self.repo.delete(user_id)

    async def list_users(self, page: int = 1, per_page: int = 20) -> dict:
        offset = (page - 1) * per_page
        users = await self.repo.list(per_page, offset)
        total = await self.repo.count()

        return {
            "items": [u.to_dict() for u in users],
            "page": page,
            "per_page": per_page,
            "total": total,
            "total_pages": (total + per_page - 1) // per_page,
        }

Auth Routes

# routes/auth.py
from flaxon import Router, HTTPException, Response
from flaxon.security import JWTBackend, login_required

from schemas.user import CreateUser, LoginRequest
from services.user_service import UserService
from repositories.user_repository import UserRepository
from utils.db import get_db
from config import Config

router = Router(prefix="/api/v1/auth")
jwt_backend = JWTBackend(secret_key=Config.SECRET_KEY)

@router.post("/register")
async def register(data: CreateUser):
    db = get_db()
    repo = UserRepository(db)
    service = UserService(repo)

    try:
        user = await service.create_user(data.name, data.email, data.password)
        return {"success": True, "user": user.to_dict()}
    except ValueError as exc:
        raise HTTPException(400, str(exc))

@router.post("/login")
async def login(data: LoginRequest):
    db = get_db()
    repo = UserRepository(db)
    service = UserService(repo)

    user = await service.authenticate(data.email, data.password)
    if not user:
        raise HTTPException(401, "Invalid credentials")

    token = await jwt_backend.create_token(user.to_dict())
    return {"token": token, "user": user.to_dict()}

@router.post("/refresh")
@login_required
async def refresh(request):
    user = getattr(request, "user")
    token = await jwt_backend.create_token(user)
    return {"token": token}

@router.post("/logout")
@login_required
async def logout(request):
    # In production, blacklist the token
    return {"success": True}

User Routes

# routes/users.py
from flaxon import Router, HTTPException
from flaxon.security import login_required, permission_required

from schemas.user import UpdateUser
from services.user_service import UserService
from repositories.user_repository import UserRepository
from utils.db import get_db

router = Router(prefix="/api/v1/users")

@router.get("/")
@login_required
async def list_users(request):
    page = request.query.get_int("page", 1)
    per_page = request.query.get_int("per_page", 20)

    db = get_db()
    repo = UserRepository(db)
    service = UserService(repo)

    return await service.list_users(page, per_page)

@router.get("/")
@login_required
async def get_user(user_id: int):
    db = get_db()
    repo = UserRepository(db)
    service = UserService(repo)

    user = await service.get_user(user_id)
    if not user:
        raise HTTPException(404, "User not found")

    return user.to_dict()

@router.put("/")
@login_required
async def update_user(user_id: int, data: UpdateUser):
    db = get_db()
    repo = UserRepository(db)
    service = UserService(repo)

    user = await service.update_user(user_id, data.to_dict())
    if not user:
        raise HTTPException(404, "User not found")

    return user.to_dict()

@router.delete("/")
@login_required
@permission_required("delete_user")
async def delete_user(user_id: int):
    db = get_db()
    repo = UserRepository(db)
    service = UserService(repo)

    result = await service.delete_user(user_id)
    if not result:
        raise HTTPException(404, "User not found")

    return {"deleted": True}

@router.get("/me")
@login_required
async def get_me(request):
    user = getattr(request, "user")
    return user

Middleware

# middleware/auth.py
from flaxon.middleware import Middleware

class AuthMiddleware(Middleware):
    async def __call__(self, scope, receive, send):
        if scope.get("type") != "http":
            await self.app(scope, receive, send)
            return

        # Add user to scope for WebSocket authentication
        if "user" not in scope:
            scope["user"] = None

        await self.app(scope, receive, send)

# middleware/logging.py
class LoggingMiddleware(Middleware):
    async def __call__(self, scope, receive, send):
        if scope.get("type") != "http":
            await self.app(scope, receive, send)
            return

        import time
        start = time.perf_counter()
        path = scope.get("path", "/")
        method = scope.get("method", "GET")

        await self.app(scope, receive, send)

        duration = (time.perf_counter() - start) * 1000
        print(f"{method} {path} - {duration:.2f}ms")

Database Utilities

# utils/db.py
import asyncpg
from config import Config

_db_pool = None

async def init_db():
    global _db_pool
    _db_pool = await asyncpg.create_pool(Config.DATABASE_URL)

async def close_db():
    if _db_pool:
        await _db_pool.close()

def get_db():
    return _db_pool

# utils/health.py
from flaxon.health import HealthCheck

class DatabaseHealthCheck(HealthCheck):
    def __init__(self):
        super().__init__("database", self._check)

    async def _check(self):
        try:
            db = get_db()
            await db.fetch("SELECT 1")
            return {"status": "healthy"}
        except Exception as exc:
            return {"status": "unhealthy", "message": str(exc)}

Application Entry Point (Complete)

# app.py
from flaxon import Flaxon
from flaxon.middleware import RequestIDMiddleware, SecurityHeadersMiddleware, CORSMiddleware
from flaxon.security import AuthenticationMiddleware

from config import Config
from routes import users, auth
from middleware.auth import AuthMiddleware
from middleware.logging import LoggingMiddleware
from utils.db import init_db, close_db
from utils.health import DatabaseHealthCheck
from flaxon.health import HealthRegistry

app = Flaxon("myapp", debug=Config.DEBUG)
app.config.update(Config.to_dict())

# Middleware (order matters)
app.add_middleware(LoggingMiddleware)
app.add_middleware(RequestIDMiddleware)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(
    CORSMiddleware,
    allowed_origins=Config.ALLOWED_ORIGINS,
    allow_credentials=True,
)
app.add_middleware(AuthMiddleware)

# Authentication
from flaxon.security import JWTBackend
jwt_backend = JWTBackend(secret_key=Config.SECRET_KEY)
app.add_middleware(AuthenticationMiddleware, backend=jwt_backend)

# Routes
app.include_router(auth.router)
app.include_router(users.router)

# Health Checks
health = HealthRegistry()
health.register(DatabaseHealthCheck())

@app.get("/health")
async def health_check():
    return await health.check_all()

@app.get("/health/live")
async def liveness():
    return {"status": "alive"}

@app.get("/health/ready")
async def readiness():
    result = await health.check_all()
    all_healthy = all(r["status"] == "healthy" for r in result.values())
    status = 200 if all_healthy else 503
    return result, status

# Lifecycle
@app.on_startup
async def startup():
    await init_db()
    print("✅ Database connected")

@app.on_shutdown
async def shutdown():
    await close_db()
    print("✅ Database disconnected")

@app.get("/")
async def home():
    return {"message": "Welcome to MyApp API", "version": "1.0.0"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
Key Concepts
  • Separation of Concerns: Routes, Services, Repositories
  • Dependency Injection: Services receive repositories
  • Configuration: Centralized config management with env vars
  • Middleware: Auth, CORS, Request ID, Security Headers, Logging
  • Health Checks: Liveness, readiness, and database health
  • Lifecycle: Startup and shutdown hooks
  • Validation: Schemas for request validation
  • Authentication: JWT with refresh tokens
  • Authorization: Permission-based decorators