Build JSON APIs in Django Without DRF or any other library
Many students and new Django developers wonder why Django Rest Framework (DRF) is needed alongside Django. Canāt we create APIs using Django alone?
While using these libraries in sizable applications simplifies the development, this post explores Djangoās out-of-the-box capabilities to build JSON-based APIs without relying on external libraries such as DRF or Tastypie.
We will cover the following topics:
- Returning JSON Response
- Serializing queryset/model objects
- Pagination
- Handling POST/PUT/PATCH Payload
- Authentications & Permissions
- Class-based views
1. Returning JSON Response
Instead of using HttpResponse
directly, make two changes -
1. Convert the Dict object to a JSON object using json.dumps()
2. Explicitly set the content_type to "application/json"
import json
from django.http import HttpResponse
def index(request):
return HttpResponse(
json.dumps({'message': "Hello Gaurav!"}),
content_type="application/json"
)
However, youāll notice the additional code needed for each JSON response. To avoid this Django offers a class named JsonResponse
, which streamlines returning JSON responses without the need for explicit JSON conversion and content type setting each time.
from django.http import JsonResponse
def index(request):
return JsonResponse({'message': "Hello Gaurav!"})
We can also verify the response Content-Type using a simple curl.
curl -i http://127.0.0.1:8000/
2. Serializing queryset/model objects
There are many ways to serialize a Django object or a queryset. Some of them are ā
- Manually serialize the object.
- Use the inbuilt Django-provided serializer to serialize the object.
- Use 3rd-party libraries such as DRF.
As we aim for a Pure Django approach, we wonāt consider the third option.
- Manually Serialize the object.
questions = Question.objects.values_list('id', 'question_text', named=True)
data = [{'id': question.id, 'question_text': question.question_text} for question in questions]
Notice that I utilized the named=True parameter to retrieve the queryset data as named tuples, simplifying attribute access. However, for larger tables or applications with intricate table relationships, this approach might become cumbersome and insufficient.
2. Using an inbuilt serializer
from django.core.serializers import serialize
# Provide the serialize type such as python, json, xml, yaml, etc.
# Here we are using 'python' because JsonResponse will
# automatically convert it to 'json'
serialize('python', Question.objects.all())
The challenge here is that it returns extra fields that we may not want to expose to users. To address this, customize the serialized data by excluding those fields.
Additionally, Modify the behavior of inbuilt Serializers by extending the relevant Serializer classes and creating custom serializers as required.
from django.core.serializers.python import Serializer as PythonSerializer
class Serializer(PythonSerializer):
# override desired methods and add required logic
...
And then register it through the settings as explained here.
SERIALIZATION_MODULES = {
'python': 'path.to.new.python.serializer'
}
Read more about serializers here.
3. Pagination
In production applications, managing pagination is crucial. Django offers a robust pagination feature to address this need.
>>> from django.core.paginator import Paginator
>>>
>>> Question.objects.count()
13
>>> questions_per_page = 5 # Each page would have 5 questions
>>>
>>> paginator = Paginator(Question.objects.all(), questions_per_page)
>>>
>>> paginator.num_pages # Total number of pages given 5 questions in each page
3
>>> page = paginator.page(1) # Accessing page number 1
>>>
>>> page
<Page 1 of 1>
>>>
>>> page.object_list # To get all the objects in page 1
[{....}]
Combining both serializer and pagination functionalities is possible.
>>> serialized_data = serialize('python', page)
Read more about pagination here.
4. Handling POST/PUT/PATCH Payload
To retrieve JSON data from a POST request, access it via the request object and convert it into a Python object.
import json
def post(self, request):
post_body = json.loads(request.body)
question_text = post_body['question_text']
question_id = post_body['id']
...
5. Authentications & Permissions
If your app requires a personalized authentication system, create a custom Authentication backend class and include it in the settings as demonstrated below. E.g. for a custom Token authentication mechanism
from django.contrib.auth.backends import BaseBackend
class TokenBackend(BaseBackend):
def authenticate(self, request, **kwargs):
# Check the Token and return a user.
# Override other methods (e.g. has_perm()) to manage permissions
AUTHENTICATION_BACKENDS = ['path.to.TokenBackend']
Read more about authentication and permissions here.
6. Class-based views
DRF offers some powerful class-based views (CBV)(and viewsets) that are built upon Djangoās built-in CBV. Using plain CBV can be tedious. Django provides some generic CBV to help with that.
Instead of using from django.views import View
you can use below -
from django.views.generic import (
DetailView,
ListView,
CreateView,
UpdateView,
DeleteView
)
Read more about CBV here.
Conclusion
In larger applications, opting for DRF is more practical. It streamlines data serialization/deserialization, and abstracts away API boilerplate, enabling rapid API development. With this foundational knowledge, youāre geared up to conquer new challenges š. If you want me to write about a topic that is not covered here, do let me know in the comments.
For more such articles, follow me at: https://gauravvjn.medium.com.