Django is the web framework most widely used by Python developers. Django offers a very powerful toolkit for creating web applications, making it easier for developers to create complex web applications effectively and efficiently.
However, Django has weaknesses. When it comes to performance, flexibility of project structure, compatibility with other libraries, or creating microservices, Django is not the main choice, and usually Python developers will choose to use FastAPI. Why?
FastAPI excels at building APIs and real-time applications, utilizing asynchronous programming and type hints, while Django, with its full-stack framework features, is better suited to traditional web applications with admin panels, user authentication, and more.
But, what if you could leverage the benefits of Django and get performance similar to FastAPI? I will introduce you to Django-ninja, a game-changing framework that seamlessly bridges this gap, offering web developers a versatile tool to build high-performance web APIs without compromising the power and flexibility of Django. For me, it was a combination between Django and FastAPI.
So, what is Django-ninja?
Django Ninja is a Django API framework that is heavily influenced by FastAPI, a popular Python web framework. It offers a variety of features, many of which are inspired by FastAPI, such as support for asynchronous programming, type-hinted and schema-based request and response validation, automatic API documentation, and a simple, intuitive, and Pythonic way to define REST API routes.
Django Ninja provides an alternative to Django REST Framework (DRF), which is another popular Django API framework. DRF is a powerful tool, but Django Ninja is a more lightweight and appealing alternative that may be a better fit for some Django projects.
For more information, you can visit their Homepage.
In this post, I will show you how to create a simple CRUD API with Django-ninja and see how the code is just like FastAPI. To create a simple CRUD API with Django Ninja, we can follow these steps:
Create Django Project
First, I start by creating a folder called django_ninja_example and installing Django Ninja dependencies.
pip install django-ninja
And then, create a new Django project inside the folder django_ninja_example.
django-admin startproject notes_app
Create Django App
After that, we will need to create our Django app. We will create our app inside the folder notes_app. First, we need to create folder notes inside the notes_app, as follows.
mkdir notes_app/notes
Then, create the Django app using
django-admin startapp notes notes_app/notes
If you succeed, the folder structure will be like this.
django_ninja_example
├── manage.py
└── notes_app
├── __init__.py
├── asgi.py
├── notes
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── settings.py
├── urls.py
└── wsgi.py
Create a Django model
First, we need to change the notes_app/notes/apps.py
into this.
class PagesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'notes_app.notes'
And also we need to add our app into INSTALLED_APPS in notes_app/settings.py
.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'notes_app.notes' # this is what we need to add
]
Finally, we can start to create our model. We will create model Notes. Open notes_app/notes/apps.py
and write this code.
class Notes(models.Model):
title = models.CharField(max_length=30)
text = models.TextField()
Next, we will create the Schema. In traditional Django, we call it serializer. The schema structure is like what we found in FastAPI, this is because they both use Pydantic.
Create a file named schemas.py in notes_app/notes and write this code.
from ninja import Schema, ModelSchema
from notes_app.notes.models import Notes
class NoteResponseSchema(Schema):
id: int
title: "str"
class NoteDetailResponseSchema(ModelSchema):
class Config:
model = Notes
model_fields = "__all__"
class NoteInputSchema(Schema):
title: "str"
text: str
In the code above I create three schemas. The first schema is used to get a note list, the second one is when get a single note, and the last one is used when we want to create or edit a note.
Creating Route
For the next, let's create the API route. We will create a simple CRUD API. Let's create a file named api.py
in the folder notes_app/notes
, and write the code below.
from typing import List
from ninja import Router
from notes_app.notes.models import Notes
from notes_app.notes.schemas import (
NoteResponseSchema,
NoteDetailResponseSchema,
NoteInputSchema,
)
router = Router()
@router.get("", response=List[NoteResponseSchema])
def view_list_notes(request):
return Notes.objects.all()
@router.get("/{notes_id}", response=NoteDetailResponseSchema)
def view_note(request, notes_id: int):
return Notes.objects.get(id=notes_id)
@router.post("", response=NoteDetailResponseSchema)
def create_note(request, payload: NoteInputSchema):
note = Notes.objects.create(title=payload.title, text=payload.text)
return note
@router.put("/{notes_id}", response=NoteDetailResponseSchema)
def edit_note(request, notes_id: int, payload: NoteInputSchema):
note = Notes.objects.get(id=notes_id)
note.title = payload.title
note.text = payload.text
note.save()
return note
@router.delete("/{notes_id}")
def delete_note(request, notes_id: int):
note = Notes.objects.get(id=notes_id)
note.delete()
return
As you can see it is very similar to FastAPI, the difference is it uses the Django ORM which for me personally is easier to use instead of using SQLalchemy or SQLModel.
You can also add pagination to the notes list by only using the pagination decorator. Here's an example:
from ninja.pagination import paginate
@router.get("", response=List[NoteResponseSchema])
@paginate
def view_list_notes(request):
return Notes.objects.all()
You can also use an async function in Django-ninja. If you using the Django version below 4.1, you need to use the sync_to_async()
adapter to use an async function. For example:
from asgiref.sync import sync_to_async
@router.get("/{notes_id}", response=NoteDetailResponseSchema)
async def view_note(request, notes_id: int):
return await sync_to_async(Notes.objects.get)(id=notes_id)
@router.post("", response=NoteDetailResponseSchema)
async def create_note(request, payload: NoteInputSchema):
note = await sync_to_async(Notes.objects.create)(
title=payload.title, text=payload.text
)
return note
For a QuerySet result, we need to use an evaluation list. For example:
@router.get("", response=List[NoteResponseSchema])
async def view_list_notes(request):
return await sync_to_async(list)(Notes.objects.all())
Django 4.1 and above come with asynchronous versions of ORM operations, so we don't need to use sync_to_async
anymore. We just need to add a prefix to the existing synchronous operation except for the QuerySet result.
Here's an example of a simple CRUD API with asynchronous operation.
from typing import List
from ninja import Router
from ninja.pagination import paginate
from notes_app.notes.schemas import (
NoteResponseSchema,
NoteDetailResponseSchema,
NoteInputSchema,
)
from notes_app.notes.models import Notes
router = Router()
@router.get("", response=List[NoteResponseSchema])
async def view_list_notes(request):
all_blogs = [note async for note in Notes.objects.all()]
return all_blogs
@router.get("/{notes_id}", response=NoteDetailResponseSchema)
async def view_note(request, notes_id: int):
return await Notes.objects.aget(id=notes_id)
@router.post("", response=NoteDetailResponseSchema)
async def create_note(request, payload: NoteInputSchema):
note = await Notes.objects.acreate(
title=payload.title, text=payload.text
)
return note
@router.put("/{notes_id}", response=NoteDetailResponseSchema)
async def edit_note(request, notes_id: int, payload: NoteInputSchema):
note = await Notes.objects.aget(id=notes_id)
note.title = payload.title
note.text = payload.text
await note.asave()
return note
@router.delete("/{notes_id}")
async def delete_note(request, notes_id: int):
note = await Notes.objects.aget(id=notes_id)
await note.adelete()
return
If you want to add pagination to the async route, it not gonna be easy but that is possible. The Django-ninja version that I currently use is 0.22.2, which still does not support async pagination. So I'm using the code that was created by chrismaille.
I created a file for the async pagination notes_app/utils/async_pagination.py and put the code there. So I'm just adding the async pagination function to the view_list_notes
.
from notes_app.utils.async_pagination import apaginate
@router.get("", response=List[NoteResponseSchema])
@apaginate
async def view_list_notes(request):
all_blogs = [note async for note in Notes.objects.all()]
return all_blogs
We already created the notes router. All we need is to add the notes router to the global router. Let's create file api.py
in the notes_app folder and write this code.
from ninja import NinjaAPI
from notes_app.notes.api import router as notes_router
api = NinjaAPI()
api.add_router("notes", notes_router)
You also need to add a Django-ninja router to urls.py.
from notes_app.api import api
urlpatterns = [
path('admin/', admin.site.urls),
...,
path('api/', api.urls)
]
Now, everything is ready. You just need to register the notes model to the database. Write this command in the Terminal.
python manage.py makemigrations
python manage.py migrate
Finally, run the code.
python manage.py runserver
If the result is like the image below, then your code is successfully running with no problem.
Then open the http://127.0.0.1/8000/api/docs or whatever host or port you use. If you open the Swagger page then you are good.
So that's it. That is how you build Django-ninja CRUD API. If you want to read more about the Django-ninja, please read the Django-ninja documentation here.
In conclusion, Django-ninja is a powerful and versatile Python web framework for building REST APIs. It is designed to be fast, simple, and easy to use, while still providing all the features you need to build robust and scalable APIs. It has the Django system and code styling like FastAPI, it was like these two combined.
However, it also has disadvantages. Compared to the other Python frameworks, Django-ninja has a smaller community and ecosystem. It means that there are fewer third-party libraries and resources available for Django Ninja, like when we want to create an asynchronous pagination. Moreover, Django-ninja is still under development, so maybe there is still a bug or missing feature.
That is my conclusion about Django-ninja, if you have any questions or opinions regarding Django-ninja or any other, feel free to write in the comments.
Top comments (0)