For a new Django API, when would you choose Django Ninja over Django REST Framework, and what would keep you on DRF?
answer
- type hints versus serializer classes
- who generates the OpenAPI document
- async support and error statuses
- existing code, team and ecosystem
basics
~20 sDjango Ninja suits typed, schema-first APIs: Pydantic validation, OpenAPI generated from type hints, native async operations. DRF suits teams already on it, and APIs leaning on viewsets, object-level permission classes, the browsable API and its third-party ecosystem. Both can share one project.
solid answer
~50 sI pick **Django Ninja** when the API is contract-first and typed: operations are functions with type hints, request and response bodies are Pydantic schemas, the OpenAPI document and docs page are built in, `async def` operations are supported, and validation failures come back as 422 with field locations. I stay on **DRF** when the project or team already uses it, when `ModelViewSet` plus routers would generate most CRUD endpoints, when authorisation leans on permission classes with `has_object_permission`, or when the browsable API and DRF's third-party packages matter. DRF views are synchronous, and its built-in OpenAPI generation is deprecated in favour of a third-party package, so schema-driven clients cost more setup there. Neither is a dead end: both mount under different URL prefixes in one Django project, so a team can start new endpoints in Ninja without rewriting DRF ones.
code
python · 14 linesfrom django.urls import include, path
from ninja import NinjaAPI
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("orders", OrderViewSet) # existing DRF ModelViewSet
api = NinjaAPI(version="2.0.0")
api.add_router("/search/", search_router) # new Ninja endpoints
urlpatterns = [
path("api/v1/", include(router.urls)),
path("api/v2/", api.urls),
]go deeper
Recall the core difference: Ninja uses type hints and Pydantic schemas, DRF uses serializer and view classes.
Explain the practical differences an API client sees: validation status codes, error shapes, documentation and pagination.
Evaluate both against a real endpoint - permissions, nested writes, async needs - and plan how the two can coexist during a migration.
Frame the choice around team skills, existing investment, client-generation needs and long-term maintenance, and set the conventions that keep a mixed codebase coherent.
## Two ways to build an API on Django Both tools sit on top of Django's request handling, ORM and settings; they differ in how an endpoint is **declared**, **validated** and **documented**. - **Django REST Framework (DRF)** declares data with `Serializer` and `ModelSerializer` classes, handles requests in `APIView` or viewset classes, and wires URLs with routers such as `DefaultRouter`. Behaviour is configured through pluggable classes - authentication, permissions, throttling, pagination, filtering - often set project-wide in its `REST_FRAMEWORK` settings. - **Django Ninja** declares endpoints as functions decorated with `@api.get` or `@router.post`, reads parameters from **type hints**, validates bodies with **Pydantic** schemas (`Schema`, `ModelSchema`), and generates an **OpenAPI** document from the same declarations. ## Side by side | concern | Django Ninja 1.7 | DRF 3.18 | |---|---|---| | data declaration | Pydantic `Schema` / `ModelSchema` | `Serializer` / `ModelSerializer` | | endpoints | typed functions on `NinjaAPI` / `Router` | `APIView`, viewsets, routers | | input validation failure | 422, list of `type`/`loc`/`msg` | 400, field-keyed error lists | | OpenAPI | built in, generated from hints; `/docs` page | built-in generator deprecated; a third-party package is recommended | | async | `async def` operations | views are synchronous | | auth | `auth=` callables; permission checks in code | authentication and permission classes, object-level permissions | | default access | public unless `auth=` is set | `AllowAny` unless configured | | browsing | Swagger or Redoc page | browsable HTML API renderer | | CRUD scaffolding | write each operation | `ModelViewSet` + router | Neither column is "better"; each is optimised for a different way of working. ## When Ninja is the stronger choice - **Contract-first APIs** consumed by generated clients, where the schema must match runtime behaviour. - **Teams fluent in type hints and Pydantic**, or moving between Django and FastAPI-style code. - **I/O-heavy endpoints** that benefit from `async def` under an ASGI server, such as a search endpoint that waits on another service. - **Small, explicit endpoints** where a function per operation reads more clearly than a class hierarchy. ## When DRF is the stronger choice - **An existing DRF codebase** or team: consistency and shared knowledge outweigh a new style. - **CRUD-heavy resources** where `ModelViewSet`, routers and filter backends produce many endpoints from little code. - **Rich authorisation** built on permission classes, including per-object checks through `has_object_permission`. - **A browsable API** for internal users, and third-party packages built around DRF's extension points. ## How to decide 1. **Start from constraints**: existing code, team skills, client-generation needs, async needs. 2. **Prototype the hardest endpoint** in the candidate, not a hello-world; permissions and nested writes reveal more than a list endpoint. 3. **Check the non-functional parts**: error-response shape, authentication story, pagination contract and schema publication. 4. **Remember coexistence**: both can be mounted under different prefixes, for example `path("api/v1/", include(drf_urls))` and `path("api/v2/", api.urls)`, so a migration can be gradual. The principal-level answer names the trade-offs and the context that tips them, rather than declaring a winner. ## What a switch actually costs - **Carries over unchanged**: models, migrations, the admin, `django.contrib.auth`, middleware, settings and most of the test suite, because both tools sit on the same Django project. - **Must be rewritten**: serializers become `Schema` or `ModelSchema` classes, viewsets become operations on routers, permission classes become `auth=` plus explicit checks, and pagination and filter classes become `@paginate` and `FilterSchema`. - **Visible to clients**: validation status and error shape, pagination parameters and keys, and the URL layout that DRF routers generated. - **Organisational**: two styles in one codebase need a written convention for which one new endpoints use, or the split never ends. That cost is why the decision is usually made per new API or per new version prefix rather than as a rewrite of working endpoints. ## Mistakes interviewers probe - Claiming Ninja cannot use the Django ORM, admin or auth - it is a layer on Django like DRF. - Claiming DRF cannot produce OpenAPI at all, or that its built-in generator is the recommended route today. - Assuming async support alone justifies a switch when endpoints are CPU-bound or already fast. - Ignoring that a mixed codebase needs agreed error formats and auth across both styles.
- What changes for API clients if a team moves an endpoint from DRF to Django Ninja?Invalid input returns 422 with a list of `type`, `loc` and `msg` entries instead of DRF's 400 with errors keyed by field; unauthenticated calls return `{"detail": "Unauthorized"}`. Pagination parameter names and shapes may differ too. Either keep the old contract with custom handlers and schemas, or version the endpoint.
- How would you run DRF and Django Ninja in one project without confusing clients?Mount them under distinct prefixes, agree one error format and one authentication scheme across both, and publish one API document per prefix. New endpoints go to the chosen tool; old ones move only when touched, with contract tests guarding each move.
saying these in an interview costs you the question
- Django Ninja replaces Django, so the ORM and admin are no longer available.
- DRF's built-in OpenAPI generator is the recommended way to document DRF today.
- DRF and Django Ninja cannot be installed in the same Django project.
- Async support alone makes Ninja the right choice for every new API.
- Both frameworks return 400 for invalid request bodies by default.