Engineering August 5, 2024 4 min read

Django Ninja vs Django Rest Framework

Photo of Pavel Danilyuk on Pexels

This article compares how Django Ninja (Ninja) and Django Rest Framework (DRF) are implemented in a Django project, highlighting the differences between schemas and serializers, filters, views, and URLs.

It does not focus on performance benchmarks.

For this purpose, we create an API to perform CRUD operations on a Category model. This will not implement authentication or authorization.

Packages

At the time of writing this article, Django 5.0.6 is used along with the following libraries:

For DRF:

For Ninja:

In Ninja’s case, there aren’t external libraries specific to Swagger and filters because those are built-in.

Project structure

The following image shows the difference between the Django app structure for Ninja and DRF. The only difference is between the files schemas.py and serializers.py.

Project structure
Django app structure for Ninja and DRF

Model

The code below presents the Category data model used in Ninja and DRF.

[Code snippet — Medium embed: code snippet]

Schemas and Serializers

It is important to note that Ninja uses Pydantic to validate data, unlike DRF, which uses its own types.

[Code snippet — Medium embed: code snippet]

[Code snippet — Medium embed: DRF serializers]

Field Validation example

The following code shows how to conduct value validation in both libraries. In this example, there is a value field — that must be greater than zero.

[Code snippet — Medium embed: Example of field validation in Ninja]

[Code snippet — Medium embed: Example of field validation in DRF]

Filters

In this API, a filter is implemented to allow categories to be searched by name.

[Code snippet — Medium embed: Ninja filter]

[Code snippet — Medium embed: DRF filter]

One of Ninja’s advantages in filtering is that it allows the usage of the same filter in other models that have the name field. DRF allows something similar but in a more verbose way since it’s inherited from the generic class with the name filter and then defines the Meta class, as shown in the next example.

[Code snippet — Medium embed: DRF filter inherit]

Views

As shown below, while DRF abstracts the programmer from all the login inherent in each API HTTP method, Ninja requires all the logic to be written. However, as shown in the second code snippet, using the django-ninja-crud (Ninja CRUD) library greatly reduces the need for code, bringing Ninja closer to the abstraction offered by DRF.

[Code snippet — Medium embed: Ninja views (without django-ninja-crud)]

Here is the same implementation as above but using the django-ninja-crud library:

[Code snippet — Medium embed: Ninja views (with django-ninja-crud)]

Although the API logic has been largely abstracted, django-ninja-crud introduces some problems.

Ninja CRUD introduces an HTTP body, by default, to every HTTP-related method. Hence, in the code example above, we can verify the declaration and the use of the remove_swagger_request_body variable in some API methods. This is done to remove the body field from Swagger, as the GET and DELETE methods do not expect any HTTP body.

For HTTP verbs that do require an ID, such as a URL parameter, the Ninja CRUD returns a status error of 500 (Internal Server Error), by default, which is not correct. To properly return the correct status, 404 (Not Found), the get_model had to be overridden.

In both previous examples, we can see the use of the different category schemas declared previously. The CategorySchema is used for responses showing all data from that model. However in POST and PUT methods, we do not want to pass the category ID in the JSON request, so for those methods, we use the CategoryCreateSchema that removes the ID field. Similarly, in PATCH, we only want to send some category fields. To do so, we use the CategoryPartialUpdateSchema, in which the ID field is also removed, and all the other fields are set as optional, to avoid Ninja validation errors in this request.

The code below shows the DRF view code:

[Code snippet — Medium embed: DRF views]

URLs

Ninja already has Swagger integrated, so unlike in DRF, it is not necessary to set any URL path to define it.

[Code snippet — Medium embed: Ninja URLs]

[Code snippet — Medium embed: DRF urls]

Final remarks

Django Ninja

Advantages:

  • It’s simpler, which makes it better for beginners because it also forces the programmer to understand the logic behind how an API works
  • Explicit API methods using decorators
  • Async support
  • Better and simpler integration with Swagger

Disadvantages:

  • Verbose in general and specific implementation cases
  • Small nuances and details can be cumbersome (i.e.: optional field definition)
  • Ninja CRUD makes development easier but introduces problems
  • Requires the declaration of several schemas if the API has several HTTP methods, which can be difficult to manage in bigger projects
  • Poor integration with Django ORM
  • Little documentation online compared to DRF

Django Rest Framework

Advantages:

  • Makes excellent use of declarative programming
  • Less verbose for general implementation cases
  • Fast implementation
  • Excellent integration with Django ORM
  • Extensive documentation online

Disadvantages:

  • Verbose in specific implementation cases
  • Requires a good understanding of how the library works, especially when implementing specific cases
  • Need to install other libraries to complement functionalities compared to Ninja (e.g.: django-filter, drf-spectacular)

What has been mentioned here largely represents a personal opinion after using and comparing both libraries. However, the choice must always fall on the programmer or team’s personal preferences, experience level with the chosen library, and the best adaptation to project requirements.