ZCore Logo

A pragmatic and complementary architectural layer built on top of FastAPI. Standardize your structure, protect your data, and manage atomic transactions—without losing your development freedom.

pip install fastapi-zcore-framework[all]

From Chaos to Structure

FastAPI is highly performant but structureless, often leading to scattered database sessions, repetitive CRUD boilerplate, and complex dependency wiring in larger teams.

ZCore acts as a structured chassis. See how ZCore streamlines standard web operations while keeping you fully in control:

from zcore import BaseRouter, BaseService, BaseRepository, Base, Zchema
from sqlalchemy.orm import Mapped, mapped_column
import uuid

# 1. Define Model & Access Scopes (Auto-generates product:create, product:view, etc.)
class Product(Base):
    __tablename__ = "products"
    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    name: Mapped[str] = mapped_column()
    price: Mapped[float] = mapped_column()
    secret_cost: Mapped[float | None] = mapped_column() # Sensitive data

# 2. Context-Aware Schema (Auto-prunes 'secret_cost' in real-time if user lacks permission)
class ProductBase(Zchema):
    __model__ = "products"
    name: str
    price: float
    secret_cost: float | None = None

# 3. Standardize Layers & Scaffold Router (Generates 7 secure CRUD & Search endpoints)
class ProductRepository(BaseRepository[Product, ProductBase, ProductBase]):
    def __init__(self, db: AsyncSession):
        super().__init__(model=Product, db=db)

class ProductService(BaseService[Product, ProductBase, ProductBase]):
    def __init__(self, repository: ProductRepository = Inject(ProductRepository)):
        super().__init__(model=Product, repository=repository)

class ProductRouter(BaseRouter[ProductBase, ProductBase]):
    model = Product
    create_schema = ProductBase
    update_schema = ProductBase
    schema_out = ProductBase
    service = ProductService
    prefix = "/products"
# To replicate just a SUBSET of ZCore's security, transaction, and search features 
# in raw FastAPI, you must manually write massive repetitive boilerplate:

from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
import uuid

# 1. Define Base Model
class Product(Base):
    __tablename__ = "products"
    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    name: Mapped[str] = mapped_column()
    price: Mapped[float] = mapped_column()
    secret_cost: Mapped[float] = mapped_column()

# 2. Multiple Pydantic models needed to manually prevent data leakage for different roles
class ProductCreate(BaseModel):
    name: str
    price: float
    secret_cost: float

class ProductOutPublic(BaseModel):
    id: uuid.UUID
    name: str
    price: float

class ProductOutAdmin(BaseModel):
    id: uuid.UUID
    name: str
    price: float
    secret_cost: float

# 3. Manual Create Endpoint (With manual transaction rollback)
@app.post("/products", response_model=ProductOutAdmin)
async def create_product(
    data: ProductCreate, 
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_current_user)
):
    if "product:create" not in user.scopes:
        raise HTTPException(status_code=403, detail="Access denied")

    product = Product(**data.model_dump())
    db.add(product)
    try:
        await db.commit() # Manual transaction commit
        await db.refresh(product)
    except Exception:
        await db.rollback() # Manual transaction rollback on failure
        raise HTTPException(status_code=400, detail="Transaction failed")
    return product

# 4. Manual Get Endpoint (With manual role-based schema projection to prevent leaks)
@app.get("/products/{id}")
async def get_product(
    id: uuid.UUID, 
    db: AsyncSession = Depends(get_db),
    user: User = Depends(get_current_user)
):
    result = await db.execute(select(Product).where(Product.id == id))
    product = result.scalar_one_or_none()
    if not product:
        raise HTTPException(status_code=404, detail="Product not found")

    # Manual permission-based data pruning
    if "product:view_sensitive" in user.scopes:
        return ProductOutAdmin.model_validate(product)
    return ProductOutPublic.model_validate(product)

# 5. Manual List Endpoint (With manual offset calculations and count queries)
@app.get("/products")
async def list_products(
    page: int = 1, 
    size: int = 20, 
    db: AsyncSession = Depends(get_db)
):
    offset = (page - 1) * size
    count_query = select(func.count()).select_from(Product)
    total = (await db.execute(count_query)).scalar_one()

    items_query = select(Product).offset(offset).limit(size)
    items = (await db.execute(items_query)).scalars().all()

    return {
        "data": [ProductOutPublic.model_validate(i) for i in items],
        "meta": {"total": total, "page": page, "size": size}
    }

Instant Frontend Integration

One of ZCore's standout features is its native schema projection. By enabling a single flag on your router, you can instantly render dynamic forms on the frontend.

Adding ?schema=true to any scaffolded POST, PUT, or PATCH endpoint returns the raw, validated JSON-Schema of that endpoint:

GET /products?schema=true
{
  "success": true,
  "message": "Schema generated successfully",
  "data": {
    "title": "ProductBase",
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "price": { "type": "number" }
    },
    "required": ["name", "price"]
  },
  "meta": {
    "restricted_fields": []
  }
}

Security Isolation

If the active user does not have permission to view or edit specific fields, ZCore automatically prunes those fields from the returned JSON-Schema in real-time.


The Request Lifecycle

ZCore coordinates your web, context, database, and transaction layers into a single, predictable journey.

The Request Lifecycle


Six Architectural Pillars

ZCore packages essential enterprise-grade patterns into optional, lightweight modules.

🛡️

Context Shielding (Zchema)

Write a single schema model. ZCore dynamically prunes input fields (preventing mass assignment) and output fields (preventing data leakage) based on active user context.

🔗

Atomic Transactions (UOW)

Group multiple operations into a single Unit of Work. Domain events are queued and only dispatched after the database transaction commits successfully.

🔍

Dynamic Search Engine

A secure query builder that translates nested JSON filters, sorting, and eager-loading into safe SQL queries with depth-limit protection against DoS.

🏗️

Scoped DI Container

Clean constructor-based dependency injection. Registers singletons, transients, and request-scoped dependencies that are auto-cleaned per request.

🚀

Progressive Routing

Scaffold 7 CRUD and search endpoints in seconds, or bypass the router completely to write standard, raw FastAPI functional endpoints.

📦

Topological Plugins

Structure your codebase into clean modules. The central kernel automatically sorts and loads plugins based on their declared dependencies.


Built for Absolute Freedom

Is ZCore another rigid framework like Django? No.

We believe in progressive disclosure. You only use what you need, when you need it. If a specific business route requires raw, custom logic, bypass the scaffolding router entirely while still using ZCore's decoupled background layers:

# Bypass the scaffolding BaseRouter and write standard, functional FastAPI
@app.post("/custom-checkout")
async def checkout(
    data: CheckoutRequest,
    checkout_service: CheckoutService = Inject(CheckoutService) # Still uses Scoped DI
):
    # Execute transactional business logic
    return await checkout_service.process_order(data)

Next Steps

Ready to experience clean FastAPI architecture? Proceed to the Installation & Scaffolding guide to create your first ZCore app in seconds.