Build a production-grade Task Manager API using ZCore in under 10 minutes. This guide covers the entire journey — from an empty directory to a running server with secure, scaffolded CRUD endpoints, dynamic field masking, and database persistence.


Type
Hands-On Tutorial
Duration
~10 Minutes
Difficulty
Beginner

What We're Building

A Task Manager API that supports:

  • ✅ Automated creation, retrieval, updates, and deletion of tasks
  • ✅ Built-in keyset (cursor) and offset pagination
  • ✅ Advanced relational and conditional searches out-of-the-box
  • ✅ Context-aware data masking to protect sensitive fields (like assignee emails)
  • ✅ Scalable execution layers with structured lifecycle hooks

By the end, you'll have 7 fully functional endpoints with namespaced authorization actions, running atop ZCore's Scoped Dependency Injection and Plugin lifecycle architecture.


Step 1: Install & Initialize

Create a clean virtual environment and install the framework along with its database and cryptographic extras:

# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install ZCore with all optional dependencies
pip install fastapi-zcore-framework[all]

# Initialize a new project
zc init taskmanager
cd taskmanager

The zc init command generates the following clean root structure:

taskmanager/
├── main.py              # Application entry point & orchestration
├── .env                 # Project environment settings (including secure SECRET_KEY)
├── requirements.txt     # Dependencies list
├── .gitignore           # Python, IDE, and DB exclusions
└── zcore_dev.db         # Local development SQLite database

Step 2: Scaffold the Tasks Domain Module

ZCore enforces a modular, domain-driven architecture. Each feature context lives in its own directory, grouping its data model, projection schemas, persistence unit, business logic, and routing orchestrator into a single package.

Generate the tasks domain module using the built-in scaffolding tool:

zc startapp tasks --template

This commands scaffolds a clean, layered structure inside the tasks directory:

tasks/
├── __init__.py
├── models.py            # SQLAlchemy Declarative Model
├── schemas.py           # Context-Aware Zchema Definitions
├── repositories.py      # Persistence & Query Layer
├── services.py          # Pure Business Logic & Lifecycle Hooks
├── routers.py           # Scaffolded CRUD Router
└── plugin.py            # Kernel Lifespan Plugin Wrapper

Let's modify each component to build our task manager.


Step 3: Define the Database Model

Open tasks/models.py and configure the database table schema. We will use modern SQLAlchemy 2.0 type mapping:

import uuid
from sqlalchemy import String, Boolean
from sqlalchemy.orm import Mapped, mapped_column
from zcore import Base

class Task(Base):
    __tablename__ = "tasks"

    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    title: Mapped[str] = mapped_column(String(255), index=True)
    description: Mapped[str | None] = mapped_column(String(1024), default=None)
    priority: Mapped[str] = mapped_column(String(50), default="medium")
    is_completed: Mapped[bool] = mapped_column(Boolean, default=False)
    assignee_email: Mapped[str | None] = mapped_column(String(255), default=None)

Auto-Generated Permission Scopes

By inheriting from Base, ZCore automatically exposes a descriptive Actions descriptor on the model based on its __tablename__ [setup.py]:
- Task.actions().CREATE"tasks:create"
- Task.actions().VIEW"tasks:view"
- Task.actions().UPDATE"tasks:update"
- Task.actions().DELETE"tasks:delete"
- Task.actions().LISTVIEW"tasks:listview"


Step 4: Define Context-Aware Schemas

Open tasks/schemas.py. We will implement Zchema instead of Pydantic's raw BaseModel to handle data-masking at the serialization boundary:

from pydantic import ConfigDict
import uuid
from zcore import Zchema

class TaskBase(Zchema):
    __model__ = "tasks"  # Binds schema to the "tasks" permission namespace

class TaskCreate(TaskBase):
    title: str
    description: str | None = None
    priority: str = "medium"
    assignee_email: str | None = None

class TaskUpdate(TaskBase):
    title: str | None = None
    description: str | None = None
    priority: str | None = None
    is_completed: bool | None = None
    assignee_email: str | None = None

class TaskResponse(TaskBase):
    id: uuid.UUID
    is_completed: bool

    model_config = ConfigDict(from_attributes=True)

Unified Schema Projection & Mass Assignment Protection

In traditional FastAPI, developers suffer from "Schema Explosion"—creating separate classes like TaskCreate, TaskUpdate, TaskPublicResponse, and TaskAdminResponse just to handle field-level permissions.

With ZCore's Zchema, you can dramatically collapse this hierarchy [projection.py]. By defining a single, unified schema with all potential attributes, ZCore's filter_restricted_inputs and secure_serializer hooks automatically intercept requests [projection.py]. If "tasks.assignee_email" is added to the active restricted_fields context: 1. Input Shielding: If a malicious user tries to POST or PUT "assignee_email", ZCore silently prunes it before validation, preventing Mass Assignment exploits [projection.py]. 2. Output Masking: When returning data, ZCore automatically strips "assignee_email" from the JSON response before serialization, preventing Data Leakage [projection.py].

This allows you to manage security dynamically at runtime using a single source of truth, rather than maintaining dozens of redundant Pydantic models.


Step 5: Configure the Repository Layer

Open tasks/repositories.py. The repository abstracts raw database operations using the asynchronous SQLAlchemy engine:

from zcore import BaseRepository
from sqlalchemy.ext.asyncio import AsyncSession
from .models import Task
from .schemas import TaskCreate, TaskUpdate

class TaskRepository(BaseRepository[Task, TaskCreate, TaskUpdate]):
    def __init__(self, db: AsyncSession):
        super().__init__(model=Task, db=db)

No query code is required. BaseRepository automatically provides high-performance implementations of get, get_by_ids (with instant fast-path validation), get_list (supporting offset & keyset cursors), create, update, delete, and search [repository.py].


Step 6: Implement the Business Service

Open tasks/services.py to write your core business logic and lifecycle hooks:

from zcore import BaseService, Inject
from .models import Task
from .schemas import TaskCreate, TaskUpdate
from .repositories import TaskRepository

class TaskService(BaseService[Task, TaskCreate, TaskUpdate]):
    def __init__(self, repository: TaskRepository = Inject(TaskRepository)):
        super().__init__(model=Task, repository=repository)

    # --- Lifecycle Hooks ---

    async def pre_create(self, schema: TaskCreate) -> None:
        """Executed automatically inside the write pipeline before DB insertion."""
        if schema.priority not in ["low", "medium", "high"]:
            schema.priority = "medium"

    async def post_create(self, model: Task) -> None:
        """Executed after successful DB insertion, prior to transaction commit."""
        # Clean boundary for publishing events, audit logs, or notifications
        pass

The Inject(TaskRepository) annotation instructs ZCore's Scoped DI Container to resolve the repository automatically within the active request context [di.py].


Step 7: Scaffold the Automated Router

Open tasks/routers.py. We will extend BaseRouter to generate 7 secure CRUD and search endpoints:

from zcore import BaseRouter
from .schemas import TaskCreate, TaskUpdate, TaskResponse
from .services import TaskService
from .models import Task

class TaskRouter(BaseRouter[TaskCreate, TaskUpdate]):
    model = Task
    create_schema = TaskCreate
    update_schema = TaskUpdate
    schema_out = TaskResponse
    service = TaskService

    prefix = "/tasks"
    tags = ["Tasks"]
    expose_schemas = True  # Activates runtime schema introspection

# Instantiate for plugin integration
router_instance = TaskRouter()

By default, the router uses ZCoreAPIRoute to ensure responses are serialized using our custom high-performance encoder, automatically appending the Vary HTTP header to prevent caching leaks when field masking is active [api_router.py].


Step 8: Package as a Kernel Plugin

Open tasks/plugin.py. This class packages the tasks domain and defines its startup and shutdown dependencies:

from fastapi import FastAPI
from zcore import Plugin
from .routers import router_instance

class TaskPlugin(Plugin):
    name = "tasks"
    version = "0.1.0"
    dependencies = []  # Declare other plugin names that must boot up first

    def setup(self, app: FastAPI) -> None:
        # Mount the scaffolded APIRouter instance to the FastAPI app
        app.include_router(router_instance.router)

    async def on_startup(self) -> None:
        """Standard startup lifecycles (e.g. warming up caches)"""
        pass

    async def on_shutdown(self) -> None:
        """Graceful cleanup tasks during application teardown"""
        pass

Step 9: Wire the App in main.py

Open your root main.py file. This acts as the declarative orchestrator of the entire stack:

from fastapi import FastAPI
from zcore import Kernel, settings
from zcore.web import RequestLogMiddleware, ScopedDependencyMiddleware
from zcore.exceptions import app_exception_handler, AppException
from zcore.db import db_manager, register_db_event_dispatcher
from zcore.logging import setup_logging

from tasks.plugin import TaskPlugin

# 1. Initialize structured logging configuration
setup_logging()

# 2. Configure the database connection pool (SQLite development fallback)
db_manager.init_app(
    db_url=settings.DATABASE_URL,
    echo=(settings.ENVIRONMENT == "development")
)

# 3. Create the Kernel and register domain plugins
kernel = Kernel()
kernel.add_plugin(TaskPlugin())

# 4. Bridge database events to the central kernel dispatcher
register_db_event_dispatcher(kernel.dispatcher)

# 5. Create FastAPI app bound to ZCore's topological lifespan
app = FastAPI(
    title=settings.PROJECT_NAME,
    lifespan=kernel.lifespan
)

# 6. Finalize kernel plugin setups
kernel.setup(app)

# 7. Add core architectural middlewares & exception handlers
app.add_middleware(RequestLogMiddleware)
app.add_middleware(ScopedDependencyMiddleware)
app.add_exception_handler(AppException, app_exception_handler)

Step 10: Run and Verify

Launch the development server with automated reload tracking:

zc run
📡 Starting ZCore Dev Server on 127.0.0.1:8000...
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Application startup complete.

Open a new terminal session and test the scaffolded endpoints:

curl -X POST http://127.0.0.1:8000/tasks/ \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Evaluate ZCore Framework",
    "description": "Thoroughly test repository patterns and field masking",
    "priority": "high",
    "assignee_email": "architect@zcore.io"
  }'

========= Structured Response Envelope: =========
{
  "success": true,
  "message": "Success",
  "data": {
    "id": "e8a93cb4-5fa7-4f67-a068-d06e2ea851c2",
    "title": "Evaluate ZCore Framework",
    "description": "Thoroughly test repository patterns and field masking",
    "priority": "high",
    "assignee_email": "architect@zcore.io",
    "is_completed": false
  },
  "meta": null
}
curl http://127.0.0.1:8000/tasks/

========= Response =========
{
  "success": true,
  "message": "Success",
  "data": [
    {
      "id": "e8a93cb4-5fa7-4f67-a068-d06e2ea851c2",
      "title": "Evaluate ZCore Framework",
      "description": "Thoroughly test repository patterns and field masking",
      "priority": "high",
      "assignee_email": "architect@zcore.io",
      "is_completed": false
    }
  ],
  "meta": null
}

To generate dynamic forms on your frontend without exposing restricted structures, fetch the OpenAPI JSON Schema directly from the endpoint via ?schema=true:

curl http://127.0.0.1:8000/tasks/?schema=true

========= Context-Aware Schema Response: =========
{
  "success": true,
  "message": "Schema generated successfully",
  "data": {
    "title": "TaskResponse",
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "description": { "type": "string" },
      "priority": { "type": "string", "default": "medium" },
      "assignee_email": { "type": "string" },
      "id": { "type": "string", "format": "uuid" },
      "is_completed": { "type": "boolean" }
    },
    "required": ["title", "id", "is_completed"]
  },
  "meta": {
    "restricted_fields": []
  }
}

Comparison Summary

Built-In Feature Layer ZCore Code Volume Standard FastAPI Equivalent
Declarative Model ~12 lines (models.py) ~12 lines
Masking Schemas ~20 lines (schemas.py) ~70 lines (Separate models per-role)
Persistence Repos ~5 lines (repositories.py) ~90 lines (Manual connection and queries)
Orchestrated Service ~15 lines (services.py) ~60 lines (Manual context and commits)
CRUD Router ~12 lines (routers.py) ~120 lines (Routing, auth, and pagination)
Plugin Registry ~12 lines (plugin.py) N/A (Main file spaghetti)
Total Footprint ~76 lines ~350+ lines

ZCore not only saves hundreds of lines of repetitive code, but it also natively enforces thread-safe scoping, automated permission mappings, transactional safety, and real-time field masking across your entire ecosystem.