pypi django-ninja 1.6.0b1
1.6.0 beta1

18 hours ago

Note: This is beta release which is still testing - we encorage you to test this release as well and provide feedback

What's New

Idempotent Router(s)

Routers are now reusable and can be mounted to multiple APIs or multiple times within the same API. Decorators, auth, tags, and throttle settings are fully isolated between mounts.

router = Router(tags=["shared"])

@router.get("/items")
def list_items(request):
    return [{"id": 1}]

# Mount same router to multiple APIs
api_v1 = NinjaAPI(urls_namespace="v1")
api_v1.add_router("/", router)

api_v2 = NinjaAPI(urls_namespace="v2")
api_v2.add_router("/", router) # !!! Before this was giving an error

Cursor Pagination

New CursorPagination class for stable pagination over frequently changing datasets. Uses base64-encoded cursor tokens instead of offsets, ensuring consistent results even when items are added or removed.

from ninja.pagination import paginate, CursorPagination

@api.get("/events", response=list[EventSchema])
@paginate(CursorPagination, ordering=("-created",), page_size=20)
def list_events(request):
    return Event.objects.all()

Status Return

New Status class for explicitly returning HTTP status codes. Replaces the old tuple syntax (status_code, body) which is now deprecated.

from ninja import Status

@api.post("/login", response={200: Token, 401: Message})
def login(request, payload: Auth):
    if not valid:
        return Status(401, {"message": "Unauthorized"})
    return Status(200, {"token": token})

Skip Re-validation

When returning a Pydantic model instance that already matches the response schema, Django Ninja now skips redundant validation and directly serializes — a nice performance boost.

@api.get("/user", response=UserOut)
def get_user(request):
    return UserOut(id=1, name="John")  # skips re-validation

Streaming Responses (JSONL & SSE)

First-class streaming support with automatic schema validation for each chunk. Supports both JSONL and Server-Sent Events formats.

from ninja.streaming import JSONL, SSE

@api.get("/items", response=JSONL[Item])
def stream_items(request):
    for i in range(100):
        yield {"name": f"item-{i}", "price": float(i)}

@api.get("/events", response=SSE[Item])
async def stream_events(request):
    async for item in get_items():
        yield item

Details

New Contributors

Full Changelog: v1.5.3...v1.6.0b1

Don't miss a new django-ninja release

NewReleases is sending notifications on new releases.