How to write an API in 3 lines of code with Django REST framework

With only 3 lines of code, you can create a ready-to-use API endpoint with Django REST Framework.

There are a number of advantages to developing with Django. A key advantage of a REST API is it provide a great deal of flexibility. Together, Django REST framework is a powerful, salable, and versatile toolkit for constructing web APIs.

Summary

  1. Introduction to REST
  2. What is REST?
  3. What REST is not
  4. REST resources
  5. Why Use Django REST framework
  6. Getting Started with Django REST Framework 
    6.1 Serializers 
    6.2 Views / Viewsets 
    6.3 Routers 
    6.4 Renderers
    6.5 Authentication 
    6.6 Permissions 
    6.7 Filtering 
    6.8 Ordering 
    6.9 Custom Actions (Nested Resources)
  7. Conclusion

Who is this article for?

This article is intended for software engineers who already use Django and already have some knowledge in REST API.

If you need to create REST APIs in Django, or you already have some Django Views that behave as endpoints and need to extend / improve their behavior, then this article is for you.

What is REST?

REST — Representational State Transfer is an architectural model that is used to design distributed software architectures based on network communication. REST is one of the main models of the HTTP protocol that was described by Roy Fielding in his PhD thesis and adopted as the model to be used in the evolution of the HTTP protocol architecture.

Key principles of REST

Most introductions to REST start with a formal definition and history. I’ll skip over this and give you a simple, pragmatic definition:

REST is a set of principles that define how Web Standards such as HTTP and URLs should be used (which often differs a little from what many people currently do).

The benefit is that if you adhere to REST principles while designing your application, you will have a system that uses the Web architecture to your benefit.

The five fundamental principles of REST are as follows:

  • Give all things an Identifier
  • Link things together
  • Use standardized methods
  • Features with multiple representations
  • Communicate without status

What REST is not

Many people think that if an API returns JSON it is REST, or that REST is a protocol like HTTP or FTP, but no.

REST is an architectural MODEL based on HTTP resources like GET / HEAD / POST / PUT / DELETE.

Moreover, not only does JSON like a RESTFul API, as we’ll see later, the Django REST Framework has renderers for various return types, such as LaTEx, CSV and (the much-hated) XML.

Therefore, REST is not synonymous with JSON, although it is the most widely used renderer.

REST is also not the solution to all problems.

There are several SOAP-based solutions that for various reasons would not be able to respond to requests in a RESTFul API, either because they require HUGE work or because they simply can not conform to the established standards that make up the REST model. A good example of these are huge monolithic applications such as old ERPs that have SOAP endpoints.

REST resources

The REST architectural model is based on 3 concepts — Actions, Resources and Representations:

Resources

Features in REST refer to the SUBSTANTIVES of your API, or what you are referring to. Usually in Django, your API resources are mapped to your Models through serializers.

Example — User, Invoice, Group.

class User(models.Model):
name = models.CharField(max_length=255)
surname = models.CharField(max_length=255)
class Invoice(models.Model):
user = models.ForeignKey(User)
number = models.CharField(max_length=25)

each model can reference a resource in its REST API

Actions

The actions determine what the client that is consuming the API wishes to do with a given resource. Actions are mapped to HTTP VERBS, the most commonly used being GET / POST / PUT / PATCH / DELETE (We will not go into detail about HTTP verbs.)

RESTful principles provide strategies for dealing with CRUD actions using mapped HTTP methods

GET /users # Returns a list of users
GET /users/<id> # Returns information for a specific user
POST /users # Create a new user
PUT /users/<id> # Completely modifies a specific user
PATCH /users/<id> # Partially updates a specific user
DELETE /users/<id> # Remove a specific user

One of the great advantages of REST is that all these actions are already automatically implemented without having to create any new URL or nested action. In this way, the use of the resource is made clear and clean.

Good practice tip — Endpoint names should be in the plural. Although it may seem strange to describe a single instance in the plural, this is to keep the format of the URL consistent and not have to deal with strange pluralizations, it also improves the readability of the API for users in the future.

Representations

REST representations are how a resource’s data is displayed to whoever makes a request at a specific endpoint.

Generally, these features are displayed in JSON format (may have other formats). Basically, all the data in your models, after serialized, is represented as JSON objects on return of the request

GET /users
[
{
“id“: 1,
“name“ : “Anakin SkyWalker“,
},
{
“id“: 2,
“name“: “Luke SkyWalker“,
},
{
“id“: 3 ,
“name“: “Han Solo“,
}
]

After this brief introduction to the basic (very basic) concepts of REST APIs, let get into how (and why) we implement these patterns in a new or existing Django app.

Why Use Django REST framework

Ok, REST is cool, but why should I choose to use the Django REST Framework? Why not use another library or even just the standard Django views?

The quick answer to this question is simple: Productivity.

As the title article indicates, you only need a few lines to have an endpoint running. You do not need any magic, nor need to connect thousands of wires.

The level of abstraction that the DRF provides is high enough that you only worry about 1 of the basic principles: Resources.

In addition, there are other benefits to a Django REST Framework:

  • Browseable HTML API
  • Development team / full-time support
  • ORM and NON-ORM Data Serialization
  • Community
  • One of the best documentation among Python libs
  • Pluggable Authentication with theAuth, theAuth2 and Social Auth
  • Third Party Libs

The ecosystem around the DRF is immense, with a huge range of libraries that extend the DRF’s own default behavior (which is already a 3rd party).

Getting Started with Django REST Framework

We will now see some of the things the DRF offers out of the box, without having to install any more packages. You can delve into each of these themes in the DRF documentation itself.

As I said earlier, it has one of the best libs and even includes a tutorial on how to build your API using DRF, as well as being rich in examples and possible solutions between the problems.

Serializers

Serializers allow complex data such as querysets and model instances to be converted to native Python types so they can easily be rendered as JSON, XML, or other content types.

Serializers also provide deserialization, allowing parsed data to be converted back into complex data after validation. They are responsible for turning your models into RESOURCES in your API when connected to a ViewSet, which brings us to the next point.

from rest_framework import serializers
from main.models import Cat, Dog
class DogSerializer(serializers.ModelSerializer):
class Meta:
model = Dog
fields = (‘owner ‘ , ‘ name ‘ , ‘ birthday ‘ )
read_only_fields = ( ‘ owner ‘ ,)
class CatSerializer(serializers.ModelSerializer):
class Meta:
model = Cat
fields = (‘owner‘, ‘name‘, ‘birthday‘)
read_only_fields = (‘owner‘,)

* A serializer looks a lot like a Django model.

ViewSets

ViewSets, as the name already says, are sets of Views that abstract behaviors pertaining to actions performed by the user as the HTTP GET / POST / PUT / DELETE verbs.

At first glance, ViewSets are Class-Based Views that provide actions like .list()or .create().

With only 3 lines of code, you can create a ready-to-use endpoint that responds to ALL REST ACTIONS.

On a lower level, each view within a ViewSet represents an ACTION in their endpoint, such as .get().post().delete()and so on. You can see how DRF implements each view on this site — http://www.cdrf.co/

from rest_framework import viewsets, permissions
from main.models import Cat, Dog
from .permissions import IsOwnerOrReadOnly
from .serializers import CatSerializer, DogSerializer
# Create your views here.
class BaseViewSet ( viewsets . ModelViewSet ):
permission_classes = [permissions.IsAuthenticated, IsOwnerOrReadOnly]
def get_queryset(self):
qs = self.queryset.filter(owner=self.request.user)
return qs
def perform_create(self, serializer):
serializer.save(owner = self.request.user)
class DogViewSet(BaseViewSet):
serializer_class = DogSerializer
queryset = Dog.objects.all ()
class CatViewSet(BaseViewSet):
serializer_class = CatSerializer
queryset = Cat.objects.all ()

Views

Views are simple classes that give you more freedom in the implementation, you can create custom actions that usually do not have direct connection with Models or do not do CRUD.

from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from .permissions import IsOwnerOrReadOnly
from .models import Dog
class DogFeedView(APIView):
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (IsOwnerOrReadOnly,)

def get(self, request, pk=None):
dog = get_object_or_404(Dog, pk=pk)
dog.feed()
return Response ({“msg“: “Dog fed“, status=status.HTTP_200_OK})

Routers

The routers in DRF work by registering the registered viewsets, the class providing 2 mandatory parameters (‘prefix’, Class) the DRF automatically registers 2 routes in the following defaults. To create routes for simple views, you can add a route manually, just like in Django.

URL pattern: ^dogs/$ Name: ‘dog-list’
URL pattern: ^dogs/{pk}/$ Name: ‘dog-detail’

from rest_framework import routers
from .views import DogViewSet, CatViewSet, DogFeedView
router = routers.DefaultRouter(trailing_slash = False)
router.register (‘dogs‘, DogViewSet)
router.register (‘cats‘, CatViewSet)
urlpatterns = router.urls
urlpatterns + = [
url(r‘dogs/(?P<pk>[\d]+)/feed/$‘, DogFeedView.as_view(), name=dogfeed)
]

Authentication

There are four main authentication methods by default

  1. SessionAuthentication 
    This authentication scheme uses Django’s default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
  2. BasicAuthentication
    This authentication scheme uses HTTP Basic Authentication, signed against a user’s username and password. Basic authentication is generally only appropriate for testing.
  3. TokenAuthentication
    This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.
  4. RemoteUserAuthentication
    This authentication scheme allows you to delegate authentication to your web server, which sets the REMOTE_USER environment variable. The most common remote servers are NGINX and Apache

In addition, you can install libraries to plug in the OAuth, OAuth2, and even SocialAuth to login with Social providers.

Authentication classes can be defined in the application context or at the views / viewsets level. You can also define multiple authentication classes, making your API extremely flexible.

from rest_framework.authentication import TokenAuthentication, BasicAuthentication
( … other definition code)
class DogFeedView(APIView):
authentication_classes = (TokenAuthentication, BasicAuthentication)
(other definition code … )

And in the context of the application

REST_FRAMEWORK = {
‘DEFAULT_AUTHENTICATION_CLASSES‘: (
‘rest_framework.authentication.BasicAuthentication‘,
‘rest_framework.authentication.SessionAuthentication‘ ,
)
}

Renderers

There are several renderers available in the DRF, which display your data in the way that is most convenient for your customer.

  • Built in 
    - JSON
    - TemplateHTML
    - StaticHTML
    - BrowseableAPI
    - Admin
    - HTMLForm
    - MultiPart
  • Third Party Libraries
    - YAML
    - XML
    - JSONP
    - MessagePack
    - CSV
    - UltraJSON
    - CamelCase JSON
    - Pandas (CSV, Excel, JPG)
    - LaTex

Like most DRF modules, renderers can also be configured in the general context or at the view level, simply by entering the URL in the parameter

?format=<renderer> or http://localhost:8000/api/resource.<renderer>


( … other definition code)
from rest_framework import renderers
from rest_framework_xml.renderers import XMLRenderer
class DogFeedView(APIView):
renderer_classes = (renderers.TemplateHTMLRenderer, renderers.JSONRenderer, XMLRenderer)
(other definition code … )

Or in the context of the application

REST_FRAMEWORK = {
‘DEFAULT_RENDERER_CLASSES‘: (
‘rest_framework.renderers.JSONRenderer‘ ,
‘rest_framework.renderers.BrowsableAPIRenderer‘ ,
)
}

Permissions

Along with authentication, permissions define when and how a client can access a particular API feature. They can also be defined by view / viewset or in the general context of the application. You can also create custom permissions.

... 
( ... other definition code)
from rest_framework import renderers
from rest_framework_xml.renderers import XMLRenderer

class DogFeedView(APIView):
renderer_classes = (renderers.TemplateHTMLRenderer, renderers.JSONRenderer, XMLRenderer)
(other definition code ... )
...

And in the context of the application

REST_FRAMEWORK = {
‘DEFAULT_PERMISSION_CLASSES‘: (
‘rest_framework.permissions.IsAuthenticated‘ ,
)
}

Custom permission:

from rest_framework.permissions import BasePermission
class IsOwner(BasePermission):
def has_object_permission (self, request, view, obj ):
"""Return` True` if permission is granted, `False` otherwise.“““
return obj.user == request.user

Filtering

There are several ways to filter the content to be served according to your customer’s needs. To enable more powerful filters, you need to install an extension called django-filter.

By default, you can filter in 3 ways:

from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
“”“
This view shouldn't return a list of all the purchases. Currently for the authenticated user
““”
user = self.request.user
return Purchase.objects.filter(purchaser=user)

Filtering — Search Filter

One of the most powerful filter mechanics filters is the Search Filter, which allows the user to search on their database as if they were using Django’s ORM querysets, simply enter the “search” parameter in the URL (which can be changed in the settings)

http://example.com/api/users?search=russell

It is also possible to perform the filtering on ManyToManyand ForeignKeyrelationships in the same way that we perform reverse queries using double underscore (__)in Django querysets.

class UserListView(generics.ListAPIView):
queryset = User.objects.all ()
serializer_class = UserSerializer
filter_backends = (filters.SearchFilter,)
search_fields = (‘username‘, ‘email‘, ‘profile__profession‘)

Ordering

By default, DRF sorting follows what is determined by the models. To define other fields, simply define two attributes in the view/viewset that you want:

class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = (filters.OrderingFilter,)
ordering_fields = (‘username‘, ‘email‘)

So just tell the URL which order you want the representation to be returned http://example.com/api/users?ordering=username

Custom Actions (Nested routers)

Not always the standard HTML “verbs” solve our problem. When we are doing the study of our API, we identify verbs that do not exist by default, but we can “extend” the protocol by creating custom actions to represent these verbs. For instance, we need a verb END (Action) to end a CALL (Resource), but we do not have this verb by default in the HTTP definition, so we can “extend” the PUT / POST with a nested resource. Remember that ViewSets implement HTTP actions as methods? So…

class CallViewSet(viewsets.ModelViewSet):
… (other code definition)

@detail_route(methods=[‘put‘], URL_path=‘end-call‘)
def end_call(self, request, identifier=None):
“”“End the given call.““”
call = self.get_object()
timestamp = request.data.get(‘timestamp‘, None)
        try:
if not call.has_ended:
call.end_call ( timestamp = timestamp)
except ValueError as exc:
raise serializers.ValidationError(str(exc))
        serializer = self.serializer_class(instance=call)
return Response(serializer.data, status=status.HTTP_200_OK)

Nested resource for calling the route http://localhost:8000/api/v1/calls/<id>/end-call/

Conclusion

We have seen how Django REST Framework makes resource creation of a REST API easy, greatly increasing our productivity.

DRF is not limited to only those topics covered in this article, in the DRF documentation you have many other features related to REST APIs such as Throttling, Documentation, Validators, Versioning and much more.

DRF also has capabilities to support the HATEOAS API model. Like Django, DRF is extremely powerful, reliable, and can help you solve your problems with the least effort.

If you have questions, drop them in the comments and I’ll get back to them. My first language is Portuguese, so I’ll tackle them as I can. Thank you Google Translate. ;-)

If you’ve build something cool with Django REST API, drop that in the comments as well. I’d love to check it out.



At Crowdbotics, I help business build cool things with Django (among other things). If you have a Django project where you need additional developer resources, drop us a line. Crowbotics can you estimate build time for given product and feature specs, and provide specialized Django developers as you need them. If you’re building with Django, check out Crowdbotics.

Thank you to William Wickey for help editing and translating this post.

Like what you read? Give Andre Machado a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.