Skip to content

ninja-devx

Typed controllers and reusable resource policies for Django Ninja. The package centralizes recurring tenant/owner scoping, transaction, permission, dependency-lifetime and error-handling rules while keeping Django models and Ninja schemas.

Read the scope and engineering rationale for adoption criteria, responsibility boundaries and tradeoffs. Version 0.0.2 is an alpha release; the support policy describes the compatibility contract.

Get started See the examples Compare

class ProjectController(
    SoftDeleteMixin[Project, ProjectOut], CRUDController[Project, ProjectOut, ProjectIn]
):
    tenant_field = "workspace"  # every query scoped to the caller's workspace
    soft_delete = SoftDelete("archived_at", deleted_by="archived_by")
    etag = ETag(field="updated_at")  # 304 on reads, If-Match → 412 on writes
    search_fields = ("name",)
    ordering_fields = ("name", "updated_at")
    routes = {"restore": {"path": "/{pk}/unarchive", "permissions": [IsWorkspaceAdmin()]}}


mount(api, {"/projects": ProjectController}, prefix="/v1")

These lines give you list, retrieve, create, update, partial update, archive and unarchive endpoints, with:

  • tenant isolation
  • search and enum-checked ordering
  • conditional requests and optimistic locking
  • admin-only unarchiving
  • relation loading derived from supported schema fields
  • declared errors in the generated OpenAPI schema
  • Controllers on native routers


    @get/@post on classes, typed options, scopes, hooks, plugins. as_router() returns a ninja.Router; compatibility tests cover the operation integration.

    Controllers

  • Reusable CRUD policy


    Generic arguments configure filters, search, ordering, cursor pagination, nested resources, bulk writes, soft delete and N+1 prevention.

    CRUD

  • Layers without force


    HTTP-free services, repositories, policies and domain errors, when you need them. Three architecture recipes pass the same tests.

    Services and layers

  • CQRS without a bus


    use_case and use_query handlers, DomainEvent delivered after commit through an explicit EventBus, and UnitOfWork for multi-repository transactions.

    CQRS and DDD

  • SaaS-ready HTTP


    tenant_field, ETag/If-Match, user, scope and tenant throttles, role-based field visibility, and middleware for security headers, request hardening, response caching and pagination headers.

    Multi-tenancy

  • Async that stays cheap


    One class serves sync and async. A unit of work takes one thread hop, and lazy loads and blocking calls fail loudly.

    Async and sync

  • Contract and drift checks


    Scaffolding with model constraints, schema drift checks, system checks, schemathesis contract tests, and typed TypeScript and pydantic clients.

    Testing

  • Inspect and diagnose


    devx_inspect prints the resolved policy of a mounted controller, and @requires_related makes N+1 loading explicit.

    Inspecting controllers

The guides index maps each task to its page, and Controllers, CRUD, Permissions and Errors are the best starting points.

Principles

Native Ninja
Controllers compile into regular ninja.Routers. Parsing, validation, auth, pagination, throttling, streaming and exception handlers are Ninja's own.
Typed end to end
Options are TypedDicts and generic arguments configure CRUD. mypy strict and pyright strict pass, and the package does not use typing.Any.
Fail at startup
Configuration mistakes raise ControllerConfigError from as_router(), or show up in manage.py check, not on the first request.
No hidden state
Request data is never stored on self, and there are no global registries.
Measured
Controllers cost microseconds over function views, and an optional local budget check guards that line. See Performance.