CRUD framework for Django Ninja, sync or async
Serializer-first schemas · Relations · Filtering · Pagination · Auth · Permissions
Documentation · PyPI · Framework Comparison · Performance Benchmarks · Example Project · Issues
django-ninja-aio-crud builds complete REST APIs on top of Django Ninja. You say which fields each operation reads and writes, register a viewset, and get create, list, retrieve, update and delete endpoints with validation, pagination and interactive docs. The same serializer is also a CRUD API you can call from Python, in sync or async code.
Features
| Feature | Description | |
|---|---|---|
| ⚡ | Sync and async | Every viewset runs async by default, or sync with execution_mode = "sync" |
| 🐍 | Python CRUD API | create, get, update, destroy, model_dump and their async versions on every serializer |
| 📐 | Schemas | Declare create, update, read and detail with class Schemas and SchemaConfig |
| 🔗 | Relations | Nested reads, relations as ids, and nested writes for child objects |
| 📦 | Bulk operations | Bulk create, update and delete with per-item results |
| 🔍 | Filtering | Filter, search, ordering and pagination mixins |
| 🛡️ | Permissions | Viewset and object-level permission checks, including role-based access |
| 🗑️ | Soft delete | Mark rows as deleted instead of removing them |
| 🔑 | Authentication | JWT bearer and cookie auth, with per-method settings |
| 🪝 | Hooks | Reactive @on_create, @on_update and @on_delete hooks on your models |
| 🧰 | Django admin | Register a ModelSerializer in the admin with @register_admin |
| 🤖 | MCP server | Expose your viewsets as MCP tools |
| 🔒 | Type safety | Generic Serializer and APIViewSet classes for IDE autocomplete |
Install
pip install django-ninja-aio-crud
Requires Python 3.10-3.14, Django 5.2 or 6.0, and Django Ninja 1.7.x.
Quick start
Define the model. A ModelSerializer is a normal Django model with a Schemas
class that says which fields each operation reads and writes.
# blog/models.py
from django.db import models
from ninja_aio import ModelSerializer, SchemaConfig
class Article(ModelSerializer):
title = models.CharField(max_length=200)
body = models.TextField()
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Schemas:
create = SchemaConfig(
fields=["title", "body"],
optionals=[("is_published", bool)],
)
update = SchemaConfig(
optionals=[("title", str), ("body", str), ("is_published", bool)],
)
read = SchemaConfig(
fields=["id", "title", "body", "is_published", "created_at"],
)
Create the API and add the URLs:
# blog/api.py
from ninja_aio import NinjaAIO, APIViewSet
from .models import Article
api = NinjaAIO(title="Blog API")
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
pass
# project/urls.py
from django.urls import path
from blog.api import api
urlpatterns = [
path("api/", api.urls),
]
Run python manage.py runserver and open http://localhost:8000/api/docs.
| Method | Path | What it does |
|---|---|---|
GET |
/api/articles |
List articles, with pagination |
POST |
/api/articles/ |
Create an article |
GET |
/api/articles/{id} |
Get one article |
PATCH |
/api/articles/{id}/ |
Update some fields |
DELETE |
/api/articles/{id}/ |
Delete an article |
Keep your models as they are
Use a Serializer when you don't want to change your model's base class:
# blog/serializers.py
from ninja_aio import Serializer, SchemaConfig
from .models import Article
class ArticleSerializer(Serializer[Article]):
class Meta:
model = Article
class Schemas:
create = SchemaConfig(fields=["title", "body"])
update = SchemaConfig(optionals=[("title", str), ("body", str)])
read = SchemaConfig(fields=["id", "title", "body"])
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
serializer_class = ArticleSerializer
Sync or async
Viewsets are async by default. To serve the same endpoints with regular sync
views, set execution_mode:
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
execution_mode = "sync"
The model is also a CRUD API you can call in scripts, tasks and tests:
# Sync
article = Article.create({"title": "Draft", "body": "..."})
article = Article.update(article, {"is_published": True})
data = Article.model_dump(article)
# Async
article = await Article.acreate({"title": "Draft", "body": "..."})
article = await Article.aupdate(article, {"is_published": True})
data = await Article.amodel_dump(article)
Custom actions
from ninja_aio import APIViewSet, action, on
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
@on("publish")
async def publish(self, request, obj):
obj.is_published = True
await obj.asave(update_fields=["is_published"])
return await Article.amodel_dump(obj)
@action(detail=False, url_path="stats")
async def stats(self, request):
return {"total": await Article.objects.acount()}
This adds POST /api/articles/{id}/publish and GET /api/articles/stats.
Validators
Add Pydantic validators to a CreateValidators or UpdateValidators class:
from pydantic import field_validator
class Article(ModelSerializer):
# fields and Schemas as above
class CreateValidators:
@field_validator("title")
@classmethod
def title_not_blank(cls, value: str) -> str:
if not value.strip():
raise ValueError("Title cannot be blank")
return value.strip()
Invalid input returns 422 with the error message.
Upgrading from 2.x
Version 3 replaces the inner ReadSerializer/CreateSerializer classes and
Meta.schema_in/schema_out with a single class Schemas, adds sync
viewsets and the Python CRUD API. Follow the
migration guide to upgrade.
Links
Support
If you find this project useful, consider giving it a star or supporting development:
License
MIT License. See LICENSE.
Metadata
Release files for django-ninja-aio-crud 3.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_ninja_aio_crud-3.0.1.tar.gz | 10.3 MB | Without provenance | Details for django_ninja_aio_crud-3.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_ninja_aio_crud-3.0.1-py3-none-any.whl | Python 3 | none | any | Details for django_ninja_aio_crud-3.0.1-py3-none-any.whl |
Total release size: 10.4 MB
Release files / django_ninja_aio_crud-3.0.1.tar.gz
| Download URL | django_ninja_aio_crud-3.0.1.tar.gz |
|---|---|
| Size | 10.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
08005558e1eb71e8fb7c08a49653521c3aaf9baf16fc3449018b0acfb4d0852b
|
|
BLAKE2b-256 checksum How to use checksums |
c2bb15c5c7d48a3b624f1f3982473b4019ba194bc86e99d1af17efd989cdf189
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-requests/2.34.2
|
Release files / django_ninja_aio_crud-3.0.1-py3-none-any.whl
| Download URL | django_ninja_aio_crud-3.0.1-py3-none-any.whl |
|---|---|
| Size | 136.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4b3e38dd67d0b7eaf1914e969acd7eb576d37c629e84f3e2e5f157cb36e9e9d5
|
|
BLAKE2b-256 checksum How to use checksums |
ff7c72f96eb57b8421fb04bea21d0f7464726894bd25453578f346e5548f3816
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-requests/2.34.2
|
Security for django-ninja-aio-crud 3.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.