Create a CRUD API in Django rest framework

In this tutorial, you will learn how to create a simple CRUD(Create Retrieve Update Delete) api by using the django rest framework.

Let's get started by creating a new virtual environment and Django project.

Start a new django project

python -m venv env
env\Scripts\Activate #On Linux: source env/bin/activate
pip install django djangorestframework
django-admin startproject crudapi
cd crudapi
python manage.py startapp core

Now, add 'rest_frameowork' and our 'core.apps.CoreConfig' to INSTALLED_APPS in the settings file.

crudapi/settings.py
INSTALLED_APPS = [
    ...
    'core.apps.CoreConfig',
    'rest_framework',
]

Implement Models

Here, I am creating a model with only two fields 'name' and 'age'.

core/models.py
from django.db import models

class Employee(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()

    def __str__(self):
        return self.name

Implement Serializers

Django rest serializers work exactly like Django forms. Here we are using ModelSerializer to verify the user input and save it to the database.

core/serializers.py
from rest_framework import serializers
from core.models import Employee

class EmployeeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Employee
        fields = "__all__"

Implement Views

Here we are creating an employee list view that lets us add a new employee if the request type is POST and returns all the employees from the table if the request type is GET.

core/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

from core.serializers import EmployeeSerializer
from core.serializers import Employee

@api_view(['GET', 'POST'])
def employees_list(request):
    if request.method == 'GET':
        employees = Employee.objects.all()
        serializer = EmployeeSerializer(employees, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = EmployeeSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

In the below detail view, We perform the following operations: Get a single employee, Update the data for a single employee, Delete an employee.

core/views.py
@api_view(['GET' ,'PUT', 'DELETE'])
def employee_detail(request, pk):
    try:
        employee = Employee.objects.get(pk=pk)
    except Employee.DoesNotExist:
        return Response(status=status.HTTP_400_BAD_REQUEST)
    
    if request.method == 'GET':
        serializer = EmployeeSerializer(employee)
        return Response(serializer.data)
    
    if request.method == 'PUT':
        serializer = EmployeeSerializer(employee, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    if request.method == 'DELETE':
        employee.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Implement Urls

Now let's make the url routes for our views.

crudapi/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/employees/', include('core.urls'))
]

Here we add two URLs: one is / that is mapped to employees_list function and the other is :pk/ mapped to employee_detail.

core/urls.py
from django.urls import path
from core import views

urlpatterns = [
    path('', views.employees_list),
    path('<int:pk>/', views.employee_detail),
]

Finally, let's make the migrations and run the server.

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

Test the api's

To test the API's I am going to use a simple tool called httpie that you can install in a single pip command.

pip install httpie

Create a new employee.

http POST http://127.0.0.1:8000/api/employees/ name=shivam age=22

HTTP/1.1 201 Created
...
{
    "age": 22,
    "id": 1,
    "name": "shivam"
}

Get all the employees.

http http://127.0.0.1:8000/api/employees/

HTTP/1.1 200 OK
...
[
    {
        "age": 22,
        "id": 1,
        "name": "shivam"
    }
]

Update a employee.

http PUT http://127.0.0.1:8000/api/employees/1/ name=shivamkumraa age=22

HTTP/1.1 200 OK
...
{
    "age": 22,
    "id": 1,
    "name": "shivamkumraa"
}

Get a employee.

http http://127.0.0.1:8000/api/employees/1/

HTTP/1.1 200 OK
...
{
    "age": 22,
    "id": 1,
    "name": "shivamkumraa"
}

Delete a employee

http DELETE http://127.0.0.1:8000/api/employees/1/

HTTP/1.1 204 No Content
...

Use the Class Based Views

Similar to Django's class-based view, the rest framework provides us APIView that you can use to inherit to make your own class-based API views.

core/views.py
from rest_framework.views import APIView
from django.http import Http404

class EmployeeList(APIView):

    def get(self, request, format=None):
        employees = Employee.objects.all()
        serializer = EmployeeSerializer(employees, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = EmployeeSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Similarly we can make the Detail view as well.

core/views.py

class EmployeeDetail(APIView):

    def get_object(self, pk):
        try:
            return Employee.objects.get(pk=pk)
        except Employee.DoesNotExist:
            raise Http404

    def get(self, request, pk):
        employee = self.get_object(pk)
        serializer = EmployeeSerializer(employee)
        return Response(serializer.data)
    
    def put(self, request, pk):
        employee = self.get_object(pk)
        serializer = EmployeeSerializer(employee, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    def delete(self, request, pk):
        employee = self.get_object(pk)
        employee.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Now you need to update the URLs and call the as_view() function to the Views.

core/urls.py
...
urlpatterns = [
    path('', views.EmployeeList.as_view()),
    path('<int:pk>/', views.EmployeeDetail.as_view()),
]

To further reduce the code lines you can use the power of generic views given by the rest framework. So the below code does the exact same things as above but in a lot fewer lines of code.

core/views.py
...
from rest_framework import generics

class EmployeeList(generics.ListCreateAPIView):
    queryset = Employee.objects.all()
    serializer_class = EmployeeSerializer
    
class EmployeeDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Employee.objects.all()
    serializer_class = EmployeeSerializer