DEV Community

Cover image for Django-Ninja: Built-in Django, Feels like FastAPI
Fahmi Nurfikri
Fahmi Nurfikri

Posted on • Updated on • Originally published at betterprogramming.pub

Django-Ninja: Built-in Django, Feels like FastAPI

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
Enter fullscreen mode Exit fullscreen mode

And then, create a new Django project inside the folder django_ninja_example.

django-admin startproject notes_app
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then, create the Django app using

django-admin startapp notes notes_app/notes
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
]
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Finally, run the code.

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

If the result is like the image below, then your code is successfully running with no problem.

Terminal result

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.

Swagger docs

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)