ZCore LogoZCore

Comparisons

Honest architectural comparisons with plain FastAPI, Django, and other tools. ZCore is not a replacement — it is a composable alternative.

Choosing the right architectural pattern directly impacts the maintainability and velocity of your engineering team. ZCore is not designed to be a universal winner; it is a composable, opt-in layer designed for specific scaling patterns.

This page provides an objective, technical comparison to help you determine which paradigm fits your system architecture.

Evaluation Rule: Every tool evaluated below is highly optimized for its specific target workflow. Choose the tool that aligns with your scaling requirements, rather than adopting abstractions prematurely.


ZCore vs. Plain FastAPI

ZCore does not replace FastAPI; it runs alongside it to address common boilerplate patterns.

Architectural DecisionPlain FastAPIZCore
Underlying EngineStandard ASGI Web FrameworkStandard ASGI Web Framework + Composable Utilities
Design PhilosophyMinimalist, highly explicitMinimalist, with optional architectural abstractions
Data Access (CRUD)Manual SQL or ORM execution per routeAutomated inheritance via BaseRepository (with dialect-aware RETURNING fallback, soft-delete awareness, restoration, and forced hard-deletes)
Dependency InjectionNested, parameter-level Depends() chainsClean constructor auto-wiring via Inject[T] and IoCContainer
Field-Level SecurityMultiple separate Pydantic output schemasDynamic, role-based field pruning inside a single Zchema
Transactional BoundariesManual session.commit() executionContext-manager encapsulation via UnitOfWork (post-commit events)
Background RoutinesManual session management (risks closed session errors)@background_task / background_scope with auto-wired DI, session leak warnings, and fresh sessions
Soft DeleteManual query filtering per routeBuilt-in SoftDeleteMixin with timezone-aware timestamps, automatic scope_query, atomic batch restore, and ?force=true query support
Error NormalizationInconsistent error schemas (raw 422 arrays, HTML 500s)Unified ResponseWrapper[None] with Pydantic dot-path sanitization and debug-gated 500 tracebacks via register_exception_handlers
Structured LoggingManual configurationNon-intrusive logging with tri-split loggers (muted, passthrough, intercept), status-aware exception levels (<500 DEBUG), and slow-query tracking
Module OrganizationUndefined (User-driven)Structured, topologically sorted domain modules (Plugin)
CLI & ScaffoldingManual file setupInteractive TUI (zc) with cascading config hierarchy (CLI > .env > defaults), transparent Uvicorn passthrough, and multi-DB driver support

When to choose plain FastAPI

  • Your application has a small domain model (1–3 tables) with simple, unique queries.
  • You prefer explicit, inline code and want to avoid introducing any class-level abstractions.
  • You have no requirements for complex, role-based data masking or dynamic schema generation.
  • You are prototyping and want zero architectural layers between your database sessions and routes.

When ZCore adds value

  • Your application is expanding, and you are writing identical CRUD and bulk mutation operations for multiple models.
  • You need to dynamically prune sensitive attributes (like email, salary, or internal_notes) from responses based on active scopes, without maintaining multiple duplicate Pydantic models.
  • You must coordinate multiple database writes atomically while ensuring that side-effect events only publish once the database commit succeeds.
  • You want clean, type-hinted constructor dependency injection without nested Depends() parameter lists.
# You can mix both paradigms seamlessly in the same file
from fastapi import APIRouter
from zcore import BaseRepository, Inject, SessionDep
from .models import Task

class TaskRepo(BaseRepository[Task]):
    pass  # This route layer uses ZCore's BaseRepository

router = APIRouter()

# This route remains a standard, native FastAPI controller
# SessionDep is ZCore's Annotated[AsyncSession, Depends(get_db)]
@router.get("/health")
async def health_check(db: SessionDep):
    return {"status": "healthy"}

ZCore vs. Django / Django REST Framework

These frameworks solve fundamentally different architectural problems. Django is a batteries-included monolith, whereas ZCore is a composable architectural extension for FastAPI.

Feature BoundaryDjango + DRFZCore
Architecture StyleMonolithic, opinionatedComposable, modular, opt-in layers
Asynchronous ExecutionHistorically synchronous, with partial async adaptationsNative, fully asynchronous from the ground up (FastAPI + SQLAlchemy Async)
Data Mapping (ORM)Django ORM (tightly coupled and required)Standard SQLAlchemy 2.0 Async (fully decoupled)
Administration PortalBuilt-in, automated admin panelProjection-driven dynamic dashboard (on the roadmap)
User AuthenticationBuilt-in authentication model, session management, and UIDecoupled BaseAuth token decoding and ZContext injection
Database MigrationsBuilt-in migration engineNative integration with Alembic
API AbstractionsClass-Based Views (CBVs) and SerializersStandard Pydantic models and functional FastAPI controllers

When Django is the optimal choice

  • You are building a standard, monolithic web application (API + admin portal + server-rendered HTML templates).
  • Your project requires a fully-featured, out-of-the-box admin dashboard immediately.
  • Your team is highly productive within the Django ecosystem and prefers built-in, non-negotiable standards.
  • You do not require high-performance, fully asynchronous execution across the entire transaction lifecycle.

When ZCore is the optimal choice

  • You are building an API-first service optimized for high-throughput, async operation.
  • You want to leverage SQLAlchemy 2.0's advanced query capabilities directly without ORM-level lock-in.
  • You prefer composing your own modular abstractions over inheriting a monolithic framework.
  • You want to avoid class-based views in favor of explicit, lightweight FastAPI endpoints with automated schema projection.

ZCore vs. FastAPI-Users

FastAPI-Users is a dedicated library for comprehensive user authentication, while ZCore's BaseAuth is an abstract helper designed to bind authentication state to the active context.

FeatureFastAPI-UsersZCore (BaseAuth)
Primary FocusComplete out-of-the-box user registration and managementContextual authentication and scope-based security binding
Auth FlowsRegistration, verification, password reset, OAuth loginsJWT decoding, Argon2id hashing, and optional authentication (auto_error=False)
Password HashingTypically BcryptArgon2id (Default, high-entropy PHC winner)
User Schema & ModelEnforced user and database schema templatesBring Your Own User Model (BYOUM) matching UserProtocol (supports arbitrary ID types: int, str, UUID)
Database CouplingTightly coupled to database adapters (SQLAlchemy/Beanie)Completely decoupled; relies on standard repository interfaces
Scope of UtilitiesAuthentication onlyFull architectural stack (Repository, Service, Zchema, UoW, CLI)

When FastAPI-Users is better

  • You need a complete, turn-key authentication system, including email verification, password reset, and registration endpoints, with minimal manual coding.
  • You do not want to design or write user database structures.

When ZCore's BaseAuth is better

  • You already have an established user model and database strategy and simply need to integrate JWT claims with your request pipeline.
  • You want to seamlessly bind user permissions to ZContext.restricted_fields to drive ZCore's automated Zchema field-pruning serialization.
  • You prefer custom, lightweight authentication callbacks over relying on a third-party user-management engine.

ZCore vs. Standalone SQLAlchemy 2.0

ZCore does not replace SQLAlchemy; it builds on top of it. However, it is important to understand the level of abstraction introduced by BaseRepository and SearchEngine.

Query FeatureStandalone SQLAlchemyZCore's BaseRepository
Data Retrievalsession.execute(select(Model).where(...))repo.get(id=id) or repo.get(Model.field == val)
Mutations (Update/Delete)Requires query execution by primary keyrepo.update(), repo.delete(force=...), repo.restore(), repo.restore_multi() (accepts model instance or ID)
Bulk Insert / Update / DeleteManual insert().values().returning() setuprepo.create_multi(), repo.update_multi(), repo.delete_multi(force=...) with dialect-aware RETURNING fallback
Soft DeleteManual filter per queryInherit SoftDeleteMixin for automatic query scoping with timezone support
Keyset PaginationWritten manually per queryBuilt-in via CursorPagination and CursorParams (Base64 encoded)
Dynamic SearchHand-crafted filter parsingAutomated SQL compilation from SearchRequest payloads
Search OperatorsManual operator translationBuilt-in comparisons (eq, gt, lt, like), inverted operators (not_like, not_in, not_between, is_not_null), and boolean not grouping
Search SecurityManual validation requiredConfigurable __max_search_depth__ and restricted field checking
Execution HooksHand-written or mapped via event listenersOverridable lifecycle hooks: pre_create, pre_update, pre_delete (with force), pre_restore, post_restore (single & bulk)

No Obscurity: BaseRepository does not wrap or obscure SQLAlchemy sessions. It executes standard async SQLAlchemy statements internally. You can override any method to write custom SQL queries whenever default patterns fall short.


Where ZCore Introduces Trade-offs

A balanced architecture means acknowledging that every abstraction carries a cost:

ZCore ComponentIntroduced OverheadWhen the abstraction is unnecessary
BaseRepositoryIntroduces an extra class layer per model.Your database queries are unique, highly custom, or rarely repeat.
BaseServiceRequires learning the pre/post-action hook lifecycle.Your business logic is simple enough to reside cleanly within route controllers.
ZchemaRequires model metadata configuration and scope mappings.Your API does not serve different user roles or have field-level visibility constraints.
UnitOfWorkIntroduces context manager encapsulation for database sessions.Your API writes only to single tables within single requests.
BaseRouterGenerates 8 standard endpoints which must be configured or excluded.You only need 1 or 2 specific endpoints for a given model.
Inject[T]Requires understanding container lifecycles (Singleton, Scoped, Transient).Standard FastAPI Depends() parameters meet your modular requirements.

On this page