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 Decision | Plain FastAPI | ZCore |
|---|---|---|
| Underlying Engine | Standard ASGI Web Framework | Standard ASGI Web Framework + Composable Utilities |
| Design Philosophy | Minimalist, highly explicit | Minimalist, with optional architectural abstractions |
| Data Access (CRUD) | Manual SQL or ORM execution per route | Automated inheritance via BaseRepository (with dialect-aware RETURNING fallback, soft-delete awareness, restoration, and forced hard-deletes) |
| Dependency Injection | Nested, parameter-level Depends() chains | Clean constructor auto-wiring via Inject[T] and IoCContainer |
| Field-Level Security | Multiple separate Pydantic output schemas | Dynamic, role-based field pruning inside a single Zchema |
| Transactional Boundaries | Manual session.commit() execution | Context-manager encapsulation via UnitOfWork (post-commit events) |
| Background Routines | Manual session management (risks closed session errors) | @background_task / background_scope with auto-wired DI, session leak warnings, and fresh sessions |
| Soft Delete | Manual query filtering per route | Built-in SoftDeleteMixin with timezone-aware timestamps, automatic scope_query, atomic batch restore, and ?force=true query support |
| Error Normalization | Inconsistent 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 Logging | Manual configuration | Non-intrusive logging with tri-split loggers (muted, passthrough, intercept), status-aware exception levels (<500 DEBUG), and slow-query tracking |
| Module Organization | Undefined (User-driven) | Structured, topologically sorted domain modules (Plugin) |
| CLI & Scaffolding | Manual file setup | Interactive 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, orinternal_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 Boundary | Django + DRF | ZCore |
|---|---|---|
| Architecture Style | Monolithic, opinionated | Composable, modular, opt-in layers |
| Asynchronous Execution | Historically synchronous, with partial async adaptations | Native, 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 Portal | Built-in, automated admin panel | Projection-driven dynamic dashboard (on the roadmap) |
| User Authentication | Built-in authentication model, session management, and UI | Decoupled BaseAuth token decoding and ZContext injection |
| Database Migrations | Built-in migration engine | Native integration with Alembic |
| API Abstractions | Class-Based Views (CBVs) and Serializers | Standard 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.
| Feature | FastAPI-Users | ZCore (BaseAuth) |
|---|---|---|
| Primary Focus | Complete out-of-the-box user registration and management | Contextual authentication and scope-based security binding |
| Auth Flows | Registration, verification, password reset, OAuth logins | JWT decoding, Argon2id hashing, and optional authentication (auto_error=False) |
| Password Hashing | Typically Bcrypt | Argon2id (Default, high-entropy PHC winner) |
| User Schema & Model | Enforced user and database schema templates | Bring Your Own User Model (BYOUM) matching UserProtocol (supports arbitrary ID types: int, str, UUID) |
| Database Coupling | Tightly coupled to database adapters (SQLAlchemy/Beanie) | Completely decoupled; relies on standard repository interfaces |
| Scope of Utilities | Authentication only | Full 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_fieldsto drive ZCore's automatedZchemafield-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 Feature | Standalone SQLAlchemy | ZCore's BaseRepository |
|---|---|---|
| Data Retrieval | session.execute(select(Model).where(...)) | repo.get(id=id) or repo.get(Model.field == val) |
| Mutations (Update/Delete) | Requires query execution by primary key | repo.update(), repo.delete(force=...), repo.restore(), repo.restore_multi() (accepts model instance or ID) |
| Bulk Insert / Update / Delete | Manual insert().values().returning() setup | repo.create_multi(), repo.update_multi(), repo.delete_multi(force=...) with dialect-aware RETURNING fallback |
| Soft Delete | Manual filter per query | Inherit SoftDeleteMixin for automatic query scoping with timezone support |
| Keyset Pagination | Written manually per query | Built-in via CursorPagination and CursorParams (Base64 encoded) |
| Dynamic Search | Hand-crafted filter parsing | Automated SQL compilation from SearchRequest payloads |
| Search Operators | Manual operator translation | Built-in comparisons (eq, gt, lt, like), inverted operators (not_like, not_in, not_between, is_not_null), and boolean not grouping |
| Search Security | Manual validation required | Configurable __max_search_depth__ and restricted field checking |
| Execution Hooks | Hand-written or mapped via event listeners | Overridable 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 Component | Introduced Overhead | When the abstraction is unnecessary |
|---|---|---|
BaseRepository | Introduces an extra class layer per model. | Your database queries are unique, highly custom, or rarely repeat. |
BaseService | Requires learning the pre/post-action hook lifecycle. | Your business logic is simple enough to reside cleanly within route controllers. |
Zchema | Requires model metadata configuration and scope mappings. | Your API does not serve different user roles or have field-level visibility constraints. |
UnitOfWork | Introduces context manager encapsulation for database sessions. | Your API writes only to single tables within single requests. |
BaseRouter | Generates 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. |