Advanced Guide

Catzilla Logo

This guide covers advanced Catzilla features for building production-ready web applications. You’ll learn about dynamic routing patterns, comprehensive error handling, CLI deployment, and performance optimization.

Dynamic Routes and Patterns

Advanced Path Parameters

Catzilla’s C-powered trie router supports sophisticated path patterns:

from catzilla import App, JSONResponse

app = App()

# Nested path parameters
@app.get("/users/{user_id}/posts/{post_id}/comments/{comment_id}")
def get_comment(request):
    params = request.path_params
    return JSONResponse({
        "user_id": params["user_id"],
        "post_id": params["post_id"],
        "comment_id": params["comment_id"],
        "content": f"Comment {params['comment_id']} on post {params['post_id']}"
    })

# Mixed static and dynamic segments
@app.get("/api/v1/users/{user_id}/profile/settings")
def get_user_settings(request):
    user_id = request.path_params["user_id"]
    return JSONResponse({
        "user_id": user_id,
        "settings": {"theme": "dark", "notifications": True}
    })

# Optional-like patterns with multiple routes
@app.get("/blog")
def blog_home(request):
    return JSONResponse({"posts": ["post1", "post2", "post3"]})

@app.get("/blog/{slug}")
def blog_post(request):
    slug = request.path_params["slug"]
    return JSONResponse({
        "slug": slug,
        "title": f"Blog post: {slug}",
        "content": "Blog post content here..."
    })

if __name__ == "__main__":
    app.listen(8000)

Route Organization with Router Groups

Create modular, maintainable applications using router groups:

from catzilla import App, JSONResponse, RouterGroup

app = App()

# API versioning with router groups
v1_api = RouterGroup(prefix="/api/v1", tags=["v1"])
v2_api = RouterGroup(prefix="/api/v2", tags=["v2"])

# Authentication router
auth_router = RouterGroup(prefix="/auth", tags=["authentication"])

# Admin panel router
admin_router = RouterGroup(prefix="/admin", tags=["admin"])

# V1 API routes
@v1_api.get("/users")
def v1_list_users(request):
    return JSONResponse({"version": "v1", "users": ["alice", "bob"]})

@v1_api.get("/users/{user_id}")
def v1_get_user(request):
    user_id = request.path_params["user_id"]
    return JSONResponse({
        "version": "v1",
        "user": {"id": user_id, "name": f"User {user_id}"}
    })

# V2 API routes (enhanced)
@v2_api.get("/users")
def v2_list_users(request):
    page = int(request.query_params.get("page", "1"))
    limit = int(request.query_params.get("limit", "10"))

    return JSONResponse({
        "version": "v2",
        "users": [f"user_{i}" for i in range(page*limit, (page+1)*limit)],
        "pagination": {"page": page, "limit": limit, "total": 100}
    })

@v2_api.get("/users/{user_id}")
def v2_get_user(request):
    user_id = request.path_params["user_id"]
    return JSONResponse({
        "version": "v2",
        "user": {
            "id": user_id,
            "name": f"User {user_id}",
            "email": f"user{user_id}@example.com",
            "created_at": "2025-01-01T00:00:00Z"
        }
    })

# Authentication routes
@auth_router.post("/login")
def login(request):
    credentials = request.json()
    return JSONResponse({
        "message": "Login successful",
        "token": "jwt_token_here",
        "user": credentials.get("username")
    })

@auth_router.post("/logout")
def logout(request):
    return JSONResponse({"message": "Logged out successfully"})

# Admin routes
@admin_router.get("/stats")
def admin_stats(request):
    return JSONResponse({
        "total_users": 150,
        "active_sessions": 45,
        "requests_today": 2500
    })

@admin_router.get("/users/{user_id}/ban")
def ban_user(request):
    user_id = request.path_params["user_id"]
    return JSONResponse({
        "message": f"User {user_id} has been banned",
        "admin_action": True
    })

# Register all router groups
app.include_router(v1_api)
app.include_router(v2_api)
app.include_router(auth_router)
app.include_router(admin_router)

# Main app routes
@app.get("/")
def api_info(request):
    return JSONResponse({
        "name": "Advanced API",
        "versions": ["v1", "v2"],
        "endpoints": {
            "v1_users": "/api/v1/users",
            "v2_users": "/api/v2/users",
            "login": "/auth/login",
            "admin": "/admin/stats"
        }
    })

if __name__ == "__main__":
    app.listen(8000)

This creates a well-organized API with clear separation of concerns:

  • /api/v1/* - Version 1 API endpoints

  • /api/v2/* - Version 2 API endpoints

  • /auth/* - Authentication endpoints

  • /admin/* - Administrative endpoints

Error Handling

Production-Ready Error Management

Catzilla provides comprehensive error handling for production applications:

from catzilla import App, JSONResponse, HTMLResponse

# Create app with production mode for clean error responses
app = App(production=True)

# Custom exception for business logic errors
class UserNotFoundError(Exception):
    def __init__(self, user_id):
        self.user_id = user_id
        super().__init__(f"User {user_id} not found")

class ValidationError(Exception):
    def __init__(self, message, field=None):
        self.field = field
        super().__init__(message)

# ==========================================
# CUSTOM ERROR HANDLERS
# ==========================================

@app.exception_handler(UserNotFoundError)
def handle_user_not_found(request, exc):
    return JSONResponse({
        "error": "User not found",
        "user_id": exc.user_id,
        "code": "USER_NOT_FOUND"
    }, status_code=404)

@app.exception_handler(ValidationError)
def handle_validation_error(request, exc):
    response_data = {
        "error": "Validation failed",
        "message": str(exc),
        "code": "VALIDATION_ERROR"
    }
    if exc.field:
        response_data["field"] = exc.field
    return JSONResponse(response_data, status_code=400)

@app.exception_handler(ValueError)
def handle_value_error(request, exc):
    return JSONResponse({
        "error": "Invalid value provided",
        "message": str(exc),
        "code": "INVALID_VALUE"
    }, status_code=400)

# ==========================================
# GLOBAL ERROR HANDLERS
# ==========================================

@app.not_found_handler
def custom_404(request):
    if request.path.startswith("/api/"):
        # JSON response for API endpoints
        return JSONResponse({
            "error": "Endpoint not found",
            "path": request.path,
            "code": "NOT_FOUND"
        }, status_code=404)
    else:
        # HTML response for web pages
        return HTMLResponse("""
            <html>
                <head><title>Page Not Found</title></head>
                <body>
                    <h1>404 - Page Not Found</h1>
                    <p>The page you're looking for doesn't exist.</p>
                    <a href="/">Go Home</a>
                </body>
            </html>
        """, status_code=404)

@app.server_error_handler
def custom_500(request, exc):
    # Log the error in production
    print(f"Server error: {exc}")

    return JSONResponse({
        "error": "Internal server error",
        "message": "Something went wrong on our end",
        "code": "INTERNAL_ERROR"
    }, status_code=500)

# ==========================================
# ROUTES THAT DEMONSTRATE ERROR HANDLING
# ==========================================

@app.get("/users/{user_id}")
def get_user(request):
    user_id = request.path_params["user_id"]

    # Simulate user lookup
    if user_id == "999":
        raise UserNotFoundError(user_id)

    return JSONResponse({
        "id": user_id,
        "name": f"User {user_id}",
        "email": f"user{user_id}@example.com"
    })

@app.post("/users")
def create_user(request):
    try:
        data = request.json()
    except Exception:
        raise ValidationError("Invalid JSON in request body")

    # Validate required fields
    if not data.get("name"):
        raise ValidationError("Name is required", field="name")

    if not data.get("email") or "@" not in data.get("email", ""):
        raise ValidationError("Valid email is required", field="email")

    # Simulate user creation
    return JSONResponse({
        "message": "User created successfully",
        "user": data
    }, status_code=201)

@app.get("/divide/{a}/{b}")
def divide_numbers(request):
    try:
        a = float(request.path_params["a"])
        b = float(request.path_params["b"])
    except ValueError:
        raise ValueError("Parameters must be numbers")

    if b == 0:
        raise ValueError("Cannot divide by zero")

    return JSONResponse({"result": a / b})

@app.get("/error-demo")
def trigger_server_error(request):
    # This will trigger the server_error_handler
    raise Exception("This is a demo server error")

@app.get("/")
def home(request):
    return JSONResponse({
        "message": "Error handling demo API",
        "endpoints": {
            "get_user": "/users/{user_id} (try /users/999 for 404)",
            "create_user": "POST /users (requires name and email)",
            "divide": "/divide/{a}/{b} (try /divide/10/0 for error)",
            "not_found": "/nonexistent (try any invalid path)",
            "server_error": "/error-demo"
        }
    })

if __name__ == "__main__":
    print("Starting error handling demo server...")
    app.listen(8000)

Test the error handling:

# Test various error scenarios
curl http://localhost:8000/users/999          # UserNotFoundError
curl http://localhost:8000/divide/10/0        # ValueError
curl http://localhost:8000/nonexistent        # 404 handler
curl -X POST http://localhost:8000/users      # ValidationError

Debug vs Production Mode

Control error response verbosity with production mode:

# Debug mode (development) - detailed error messages
app = App(production=False)

# Production mode - clean, safe error messages
app = App(production=True)

Debug Mode Response:

{
  "error": "Internal server error",
  "message": "division by zero",
  "traceback": "Traceback (most recent call last):\n  File...",
  "type": "ZeroDivisionError"
}

Production Mode Response:

{
  "error": "Internal server error",
  "message": "Something went wrong on our end",
  "code": "INTERNAL_ERROR"
}

Running via CLI

Command Line Deployment

Catzilla provides a built-in CLI for running applications in production:

Basic Usage:

# Run with default settings (port 8000, host 127.0.0.1)
python -m catzilla myapp:app

# Specify custom port
python -m catzilla myapp:app --port 3000

# Bind to all interfaces for external access
python -m catzilla myapp:app --host 0.0.0.0 --port 8080

# Multiple options
python -m catzilla myapp:app --host 0.0.0.0 --port 3000

App Module Structure:

Your application should be structured for CLI usage:

# myapp.py
from catzilla import App, JSONResponse

app = App(production=True)  # Production mode for CLI deployment

@app.get("/")
def home(request):
    return JSONResponse({"message": "Hello from production!"})

@app.get("/health")
def health_check(request):
    return JSONResponse({"status": "healthy"})

# No need for if __name__ == "__main__" when using CLI

Production Deployment Example:

# Development
python -m catzilla myapp:app --port 8000

# Staging
python -m catzilla myapp:app --host 0.0.0.0 --port 5000

# Production (behind reverse proxy)
python -m catzilla myapp:app --host 127.0.0.1 --port 8080

Environment Configuration

Use environment variables for configuration:

# config.py
import os

class Config:
    HOST = os.getenv("CATZILLA_HOST", "127.0.0.1")
    PORT = int(os.getenv("CATZILLA_PORT", "8000"))
    PRODUCTION = os.getenv("CATZILLA_ENV", "development") == "production"
    DEBUG = not PRODUCTION

# app.py
from catzilla import App, JSONResponse
from config import Config

app = App(production=Config.PRODUCTION)

@app.get("/")
def home(request):
    return JSONResponse({
        "message": "Hello!",
        "environment": "production" if Config.PRODUCTION else "development"
    })

@app.get("/config")
def show_config(request):
    return JSONResponse({
        "host": Config.HOST,
        "port": Config.PORT,
        "production": Config.PRODUCTION,
        "debug": Config.DEBUG
    })

Run with environment variables:

# Set environment variables
export CATZILLA_HOST=0.0.0.0
export CATZILLA_PORT=3000
export CATZILLA_ENV=production

# Run with CLI
python -m catzilla app:app

Performance Optimization

C-Accelerated Routing

Catzilla’s C core provides exceptional routing performance:

from catzilla import App, JSONResponse
import time

app = App()

# Catzilla handles hundreds of routes efficiently
# The C trie router provides O(log n) lookup performance

# Static routes
for i in range(100):
    @app.get(f"/static-route-{i}")
    def static_handler(request, route_id=i):
        return JSONResponse({"route_id": route_id, "type": "static"})

# Dynamic routes
for i in range(100):
    @app.get(f"/dynamic-{i}/{{param}}")
    def dynamic_handler(request, route_id=i):
        param = request.path_params["param"]
        return JSONResponse({
            "route_id": route_id,
            "param": param,
            "type": "dynamic"
        })

# Nested dynamic routes
@app.get("/users/{user_id}/posts/{post_id}/comments/{comment_id}")
def nested_handler(request):
    params = request.path_params
    return JSONResponse({
        "nested_params": params,
        "lookup_time": "microseconds"  # Thanks to C trie routing
    })

@app.get("/benchmark")
def benchmark_routing(request):
    start_time = time.time()

    # The route lookup itself is extremely fast due to C implementation
    # This endpoint demonstrates that even with hundreds of routes,
    # lookup time remains consistently fast

    end_time = time.time()
    return JSONResponse({
        "message": "Route lookup performance test",
        "total_routes": 200,  # 100 static + 100 dynamic
        "lookup_algorithm": "C trie-based",
        "complexity": "O(log n)",
        "response_time_ms": (end_time - start_time) * 1000
    })

if __name__ == "__main__":
    print("Starting performance demo with 200+ routes...")
    app.listen(8000)

Memory Efficiency

Optimize memory usage for high-traffic applications:

from catzilla import App, JSONResponse

# Use production mode to reduce memory overhead
app = App(production=True)

# Efficient response patterns
@app.get("/users/{user_id}")
def get_user(request):
    user_id = request.path_params["user_id"]

    # Return lightweight responses
    return JSONResponse({
        "id": user_id,
        "name": f"User {user_id}"
    })

# Avoid storing large objects in memory
@app.get("/large-dataset")
def get_large_dataset(request):
    # Stream or paginate instead of loading everything
    page = int(request.query_params.get("page", "1"))
    limit = int(request.query_params.get("limit", "50"))

    # Generate data on-demand instead of storing
    data = [{"id": i, "value": f"item_{i}"}
            for i in range((page-1)*limit, page*limit)]

    return JSONResponse({
        "data": data,
        "page": page,
        "limit": limit
    })

if __name__ == "__main__":
    app.listen(8000)

Real-World Example

Complete REST API Application

Here’s a comprehensive example that demonstrates all advanced features:

# production_api.py
from catzilla import App, JSONResponse, RouterGroup
import json
import time
from datetime import datetime

# Production-ready app with error handling
app = App(production=True)

# Exception classes
class APIError(Exception):
    def __init__(self, message, status_code=400):
        self.message = message
        self.status_code = status_code
        super().__init__(message)

class ResourceNotFound(APIError):
    def __init__(self, resource_type, resource_id):
        super().__init__(f"{resource_type} {resource_id} not found", 404)

# Global error handlers
@app.exception_handler(APIError)
def handle_api_error(request, exc):
    return JSONResponse({
        "error": exc.message,
        "timestamp": datetime.utcnow().isoformat(),
        "path": request.path
    }, status_code=exc.status_code)

@app.not_found_handler
def api_not_found(request):
    return JSONResponse({
        "error": "Endpoint not found",
        "path": request.path,
        "available_endpoints": [
            "GET /",
            "GET /api/v1/users",
            "GET /api/v1/users/{id}",
            "POST /api/v1/users",
            "GET /health"
        ]
    }, status_code=404)

# Router groups
api_v1 = RouterGroup(prefix="/api/v1", tags=["v1"])

# In-memory storage (use database in production)
users_db = {
    "1": {"id": "1", "name": "Alice", "email": "alice@example.com"},
    "2": {"id": "2", "name": "Bob", "email": "bob@example.com"}
}

# API endpoints
@api_v1.get("/users")
def list_users(request):
    page = int(request.query_params.get("page", "1"))
    limit = int(request.query_params.get("limit", "10"))
    search = request.query_params.get("search", "")

    users = list(users_db.values())

    # Filter by search term
    if search:
        users = [u for u in users if search.lower() in u["name"].lower()]

    # Pagination
    start = (page - 1) * limit
    end = start + limit
    paginated_users = users[start:end]

    return JSONResponse({
        "users": paginated_users,
        "pagination": {
            "page": page,
            "limit": limit,
            "total": len(users),
            "pages": (len(users) + limit - 1) // limit
        },
        "filters": {"search": search} if search else {}
    })

@api_v1.get("/users/{user_id}")
def get_user(request):
    user_id = request.path_params["user_id"]

    if user_id not in users_db:
        raise ResourceNotFound("User", user_id)

    return JSONResponse(users_db[user_id])

@api_v1.post("/users")
def create_user(request):
    try:
        data = request.json()
    except:
        raise APIError("Invalid JSON in request body")

    # Validation
    if not data.get("name"):
        raise APIError("Name is required")
    if not data.get("email") or "@" not in data["email"]:
        raise APIError("Valid email is required")

    # Create user
    user_id = str(len(users_db) + 1)
    user = {
        "id": user_id,
        "name": data["name"],
        "email": data["email"],
        "created_at": datetime.utcnow().isoformat()
    }

    users_db[user_id] = user

    return JSONResponse(user, status_code=201)

@api_v1.put("/users/{user_id}")
def update_user(request):
    user_id = request.path_params["user_id"]

    if user_id not in users_db:
        raise ResourceNotFound("User", user_id)

    try:
        data = request.json()
    except:
        raise APIError("Invalid JSON in request body")

    user = users_db[user_id].copy()
    user.update(data)
    user["updated_at"] = datetime.utcnow().isoformat()
    users_db[user_id] = user

    return JSONResponse(user)

@api_v1.delete("/users/{user_id}")
def delete_user(request):
    user_id = request.path_params["user_id"]

    if user_id not in users_db:
        raise ResourceNotFound("User", user_id)

    del users_db[user_id]
    return JSONResponse({"message": f"User {user_id} deleted"})

# Register router
app.include_router(api_v1)

# Main routes
@app.get("/")
def api_info(request):
    return JSONResponse({
        "name": "Production API",
        "version": "1.0.0",
        "endpoints": {
            "users": "/api/v1/users",
            "health": "/health",
            "docs": "https://docs.example.com"
        },
        "timestamp": datetime.utcnow().isoformat()
    })

@app.get("/health")
def health_check(request):
    return JSONResponse({
        "status": "healthy",
        "uptime": time.time(),
        "users_count": len(users_db),
        "timestamp": datetime.utcnow().isoformat()
    })

if __name__ == "__main__":
    print("Starting production API server...")
    print("Available endpoints:")
    print("  GET  /")
    print("  GET  /health")
    print("  GET  /api/v1/users")
    print("  GET  /api/v1/users/{id}")
    print("  POST /api/v1/users")
    print("  PUT  /api/v1/users/{id}")
    print("  DELETE /api/v1/users/{id}")
    app.listen(8000)

Deploy this production API:

# Development
python production_api.py

# Production via CLI
python -m catzilla production_api:app --host 0.0.0.0 --port 8080

Test the API:

# Get all users
curl http://localhost:8000/api/v1/users

# Get specific user
curl http://localhost:8000/api/v1/users/1

# Create new user
curl -X POST http://localhost:8000/api/v1/users \
     -H "Content-Type: application/json" \
     -d '{"name": "Charlie", "email": "charlie@example.com"}'

# Search users
curl "http://localhost:8000/api/v1/users?search=alice&page=1&limit=5"

# Health check
curl http://localhost:8000/health

This advanced example demonstrates:

  • Router Groups: Organized API structure with versioning

  • Error Handling: Custom exceptions and global error handlers

  • Dynamic Routing: Path parameters with validation

  • Request Processing: JSON parsing, query parameters, pagination

  • Production Ready: Clean error responses, health checks

  • CLI Deployment: Ready for production deployment

You now have all the tools to build high-performance, production-ready web applications with Catzilla!