In this tutorial, you’ll learn how to build a CRUD Django REST API using the Django REST Framework. We’ll specifically create an API for a feedback application that supports full CRUD functionality—allowing you to create, read, update, and delete feedback.

If you’re new to API development with Django, this guide is a great starting point! I’ve organized the steps so that even beginners can follow along with ease. Let’s dive in!

More practice:

Build a CRUD Django REST API using Django REST Framework

Run and Test the Django REST API Locally

Before we get into the API implementation, if you’d like to run the completed source code on your machine and test the Django REST API endpoints, follow the instructions below:

  • Install the latest version of Python from https://www.python.org/downloads/.
  • Clone or download the Django CRUD REST API source code from GitHub: https://github.com/wpcodevo/django-sqlite-crud-rest-api. Then, open it in your preferred IDE or text editor.
  • Run this command in the integrated terminal of your IDE or text editor to create a virtual environment in the root project directory.
    • Windows OSpython -m venv venv
    • Mac or Linux OSpython3 -m venv venv
  • After creating the virtual environment, your IDE or code editor may prompt you to activate it for the workspace folder. Select “Yes” to proceed.
    click on Yes to activate the python virtual environment
    Note: Close the previously opened terminal in your IDE and open a new one to activate the virtual environment.

    Suppose your IDE or text editor didn’t prompt you to activate the new virtual environment. In that case, you can manually activate it by running the command below in the terminal from the root directory.
    • Windows OS (Command Prompt ) – venv\Scripts\activate.bat.
    • Windows OS (Git Bash) – venv/Scripts/activate.bat.
    • Mac or Linux OS – source venv/bin/activate
  • Use pip install -r requirements.txt to install all necessary dependencies.
  • Next, apply the database migrations to the SQLite database by running python manage.py migrate.
  • Launch the Django development server by executing python manage.py runserver.
  • With the Django server running, you can test the API endpoints using an API testing tool like Postman or the Thunder Client VS Code extension.

Set Up the Django Project

First, let’s create a new directory and initialize it as a Django project. Navigate to where you store your Django source code, and open a new terminal window. Then, run the following command to create a project folder named django-sqlite-crud-rest-api and open it in VS Code. You can use any IDE or text editor of your choice if you prefer.


mkdir django-sqlite-crud-rest-api
cd django-sqlite-crud-rest-api && code .

Next, we need to create a virtual environment to isolate the dependencies for this project. To do this, open the integrated terminal in your IDE and run the following command.

  • Windows OSpython -m venv venv
  • Mac or Linux OSpython3 -m venv venv

Then, run the command below to activate the virtual environment in the current workspace.

  • Windows OS (Command Prompt ) – venv\Scripts\activate.bat.
  • Windows OS (Git Bash) – venv/Scripts/activate.bat.
  • Mac or Linux OS – source venv/bin/activate

Since we’ll be working with both Django and Django REST Framework, run the following command in the root directory console to install them in the virtual environment.


pip install django djangorestframework
  • django – A robust Python framework designed for quick, efficient web application development with a focus on clean design.
  • djangorestframework – A powerful toolkit for building Web APIs in Django, providing easy-to-use tools for authentication, serialization, and more.

Now we’re ready to initialize Django in our project, which we can do using the django-admin CLI tool. In the terminal, run the command below to create a Django project named feedback, which will serve as the foundation for our feedback application REST API.


django-admin startproject feedback .

Once the project is created, run the following command to apply the initial database migration for built-in Django apps, such as authentication and sessions.


python manage.py migrate

This command will generate the migrations and synchronize them with the SQLite database schema.

Now that our SQLite database is synchronized with the migrations, let’s start the development server to verify everything is set up correctly. Run the following command to start the Django development server.


python manage.py runserver

This will start the HTTP server at http://127.0.0.1:8000/. Simply open http://127.0.0.1:8000/ in a new tab to view the Django welcome page.

Django enables us to organize our code into reusable apps, which helps keep the project modular. For handling requests to the /api/feedback endpoints, we’ll create a dedicated app. To do so, run the following command to generate a new Django app called feedback_api.


django-admin startapp feedback_api

Next, include the rest_framework and feedback_api apps in the INSTALLED_APPS list within the feedback/settings.py file.

feedback/settings.py


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'feedback_api',
    'rest_framework'
]

Create the Django Database and Serializer Models

In this section, you’ll define a database model for the API to handle CRUD operations, as well as a serializer model that the Django REST framework will use to convert these models into JSON objects.

Database Model

In Django, a model is a built-in feature that defines the structure of the database, including the tables, columns, relationships, and constraints.

Navigate to the feedback_api directory and create a models.py file if it doesn’t already exist. Next, add the following code to the file:

feedback_api/models.py


import uuid
from django.db import models

class FeedbackModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)
    email = models.CharField(max_length=255)
    feedback = models.CharField(max_length=255, unique=True)
    status = models.CharField(max_length=255)
    rating = models.FloatField()
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "feedback"
        ordering = ['-createdAt']

    def __str__(self) -> str:
        return self.name

To improve security and prevent attackers from scanning the table with a range of integers, we used UUIDs instead of incremental integers for the table’s primary key.

In the feedback model, you’ll notice that we’ve added a unique constraint on the feedback field to prevent duplicate entries from being stored in the database.

Serializer Model

In Django REST Framework, serializers serve two primary functions:

  • Serialization: This process converts complex data types, such as Django models, querysets, or Python objects, into native Python data types. These can then be easily converted into formats like JSON, XML, or others.
  • Deserialization: This process converts incoming data, such as JSON or other input formats, into Python objects, enabling further processing like saving the data to a database.

Now that we have a good understanding of serializers, let’s create one for our feedback app. First, navigate to the feedback_api directory and create a new file named serializers.py. Then, add the following code to the newly created file:

feedback_api/serializers.py


from rest_framework import serializers
from feedback_api.models import FeedbackModel


class FeedbackSerializer(serializers.ModelSerializer):
    class Meta:
        model = FeedbackModel
        fields = '__all__'

Django REST Framework comes with a variety of serializers, but in this tutorial, we’ll focus on using the ModelSerializer.

By inheriting from the ModelSerializer class, you get the following benefits:

  • Automatically generates a set of fields for the serializer based on the model.
  • Automatically generates validators for the serializer fields.
  • Provides default implementations of the .create() and .update() methods.

Create the CRUD REST API Views in Django

With the database and serializer models ready, it’s time to create the API views of the API. To implement the CRUD functionalities on the API, we will create five API views. In Django, the major views can be categorized into function-based views (FBVs), class-based views (CBVs), and generic views. Below is a breakdown of the major views:

  • Function-Based Views (FBVs): These are the simplest type of views in Django. They are just Python functions that receive an HTTP request and return an HTTP response.
  • Class-Based Views (CBVs): These views are represented by Python classes. They provide more structure and allow for easier reuse and inheritance of behaviour.
  • Generic Views: These are pre-built views for common tasks like displaying lists, handling forms, and performing CRUD operations.

Let’s begin by adding the required module imports at the top of the feedback_api/views.py file.

feedback_api/views.py


from rest_framework.response import Response
from rest_framework import status, generics
from feedback_api.models import FeedbackModel
from feedback_api.serializers import FeedbackSerializer
import math
from datetime import datetime

Implement the GET and POST API Views

Let’s implement the Create and Get operations for CRUD. The Create operation will allow us to add a new feedback record to the database, while the Get operation will enable us to retrieve a list of feedback records. To achieve this, we will use the GenericAPIView class, which provides two methods: get() and post().

  • get(): This method fetches a paginated list of feedback from the database and returns it in JSON format to the client.
  • post(): This method processes incoming feedback data, inserts it into the database, and returns the newly created feedback to the client.

Below is the full implementation of these methods. Copy and add them to the feedback_api/views.py file:

feedback_api/views.py


class Feedback(generics.GenericAPIView):
    serializer_class = FeedbackSerializer
    queryset = FeedbackModel.objects.all()

    def get(self, request):
        page_num = int(request.GET.get("page", 1))
        limit_num = int(request.GET.get("limit", 10))
        start_num = (page_num - 1) * limit_num
        end_num = limit_num * page_num
        search_param = request.GET.get("search")
        feedback = FeedbackModel.objects.all()
        total_feedback = feedback.count()
        if search_param:
            feedback = feedback.filter(title__icontains=search_param)
        serializer = self.serializer_class(feedback[start_num:end_num], many=True)
        return Response({
            "status": "success",
            "total": total_feedback,
            "page": page_num,
            "last_page": math.ceil(total_feedback / limit_num),
            "feedbacks": serializer.data
        })

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"status": "success", "data": {"feedback": serializer.data}}, status=status.HTTP_201_CREATED)
        else:
            return Response({"status": "fail", "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

Implement the GET, PATCH, and DELETE API Views

Now, let’s implement the GET, PATCH, and DELETE operations for CRUD. Each operation involves locating a feedback record by its ID in the database and performing an action on it. Below are the methods from the GenericAPIView class to achieve this:

  • get(): This method will find a feedback record by its ID and return it to the client in JSON format.
  • patch(): This method will locate a feedback record by its ID and update the record’s fields based on the data provided in the request body. Once updated, the modified feedback record will be returned to the client.
  • delete(): This method will find the feedback by its ID and delete it from the database.

Below is the implementation of these methods. Copy and add them to the feedback_api/views.py file:

feedback_api/views.py


class FeedbackDetail(generics.GenericAPIView):
    queryset = FeedbackModel.objects.all()
    serializer_class = FeedbackSerializer

    def get_feedback(self, pk):
        try:
            return FeedbackModel.objects.get(pk=pk)
        except:
            return None

    def get(self, request, pk):
        feedback = self.get_feedback(pk=pk)
        if feedback == None:
            return Response({"status": "fail", "message": f"Feedback with Id: {pk} not found"}, status=status.HTTP_404_NOT_FOUND)

        serializer = self.serializer_class(feedback)
        return Response({"status": "success", "data": {"feedback": serializer.data}})

    def patch(self, request, pk):
        feedback = self.get_feedback(pk)
        if feedback == None:
            return Response({"status": "fail", "message": f"Feedback with Id: {pk} not found"}, status=status.HTTP_404_NOT_FOUND)

        serializer = self.serializer_class(
            feedback, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save(updatedAt=datetime.now())
            return Response({"status": "success", "data": {"feedback": serializer.data}})
        return Response({"status": "fail", "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        feedback = self.get_feedback(pk)
        if feedback == None:
            return Response({"status": "fail", "message": f"Feedback with Id: {pk} not found"}, status=status.HTTP_404_NOT_FOUND)

        feedback.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Complete API Views

Below is the complete implementation of the five CRUD API Views:

feedback_api/views.py


from rest_framework.response import Response
from rest_framework import status, generics
from feedback_api.models import FeedbackModel
from feedback_api.serializers import FeedbackSerializer
import math
from datetime import datetime

class Feedback(generics.GenericAPIView):
    serializer_class = FeedbackSerializer
    queryset = FeedbackModel.objects.all()

    def get(self, request):
        page_num = int(request.GET.get("page", 1))
        limit_num = int(request.GET.get("limit", 10))
        start_num = (page_num - 1) * limit_num
        end_num = limit_num * page_num
        search_param = request.GET.get("search")
        feedback = FeedbackModel.objects.all()
        total_feedback = feedback.count()
        if search_param:
            feedback = feedback.filter(title__icontains=search_param)
        serializer = self.serializer_class(feedback[start_num:end_num], many=True)
        return Response({
            "status": "success",
            "total": total_feedback,
            "page": page_num,
            "last_page": math.ceil(total_feedback / limit_num),
            "feedbacks": serializer.data
        })

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response({"status": "success", "data": {"feedback": serializer.data}}, status=status.HTTP_201_CREATED)
        else:
            return Response({"status": "fail", "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

class FeedbackDetail(generics.GenericAPIView):
    queryset = FeedbackModel.objects.all()
    serializer_class = FeedbackSerializer

    def get_feedback(self, pk):
        try:
            return FeedbackModel.objects.get(pk=pk)
        except:
            return None

    def get(self, request, pk):
        feedback = self.get_feedback(pk=pk)
        if feedback == None:
            return Response({"status": "fail", "message": f"Feedback with Id: {pk} not found"}, status=status.HTTP_404_NOT_FOUND)

        serializer = self.serializer_class(feedback)
        return Response({"status": "success", "data": {"feedback": serializer.data}})

    def patch(self, request, pk):
        feedback = self.get_feedback(pk)
        if feedback == None:
            return Response({"status": "fail", "message": f"Feedback with Id: {pk} not found"}, status=status.HTTP_404_NOT_FOUND)

        serializer = self.serializer_class(
            feedback, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save(updatedAt=datetime.now())
            return Response({"status": "success", "data": {"feedback": serializer.data}})
        return Response({"status": "fail", "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        feedback = self.get_feedback(pk)
        if feedback == None:
            return Response({"status": "fail", "message": f"Feedback with Id: {pk} not found"}, status=status.HTTP_404_NOT_FOUND)

        feedback.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Register the REST API Endpoints

Now, we’ll set up routes that link to the API views created in the previous section. This will allow Django to handle incoming CRUD requests by invoking the appropriate view functions.

Add the CRUD API URLs

To map the CRUD endpoints to our API views, create a urls.py file within the feedback_api directory and add the following code:

feedback_api/urls.py


from django.urls import path
from feedback_api.views import Feedback, FeedbackDetail

urlpatterns = [
    path('', Feedback.as_view()),
    path('<str:pk>', FeedbackDetail.as_view())
]

Add the Base URL

Next, we need to define a base URL that references the URLs in the feedback_api app. Open the feedback/urls.py file and add the following code:

feedback/urls.py


from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/feedbacks/', include('feedback_api.urls'))

]

Set Up CORS on the Server

At this stage, we have completed the CRUD functionality of our API. The next step is to enable Cross-Origin Resource Sharing (CORS) on the Django server. Adding CORS middleware will allow the server to accept requests from specified cross-origin domains, like a frontend app’s domain.

Start by installing the django-cors-headers library with:


pip install django-cors-headers

Include the corsheaders package in the INSTALLED_APPS list.

feedback/settings.py


INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

Next, add the necessary CORS-related middleware to the middleware stack. Be sure to place CorsMiddleware before any middleware that generates responses.

feedback/settings.py


MIDDLEWARE = [
    ...,
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...,
]

Now, let’s configure the CORS middleware settings by specifying the allowed origins for cross-origin requests.

feedback/settings.py


CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000"
]
CORS_ALLOW_CREDENTIALS = True

Setting CORS_ALLOW_CREDENTIALS = True allows the server to include cookies, HTTP authentication, and other credentials in cross-origin requests.

Your feedback/settings.py file should now look something like this:

feedback/settings.py


"""
Django settings for feedback project.

Generated by 'django-admin startproject' using Django 5.1.3.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-r#k+$x+n2s#87)^%^(*&wu^@k(_tm%(%x&vg_jnkcwim0f&n7q'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000"
]
CORS_ALLOW_CREDENTIALS = True



# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'feedback_api',
    'rest_framework',
]


MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
]

ROOT_URLCONF = 'feedback.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'feedback.wsgi.application'


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Generate the Database Migrations

Now that we’ve completed the API, let’s generate a migration file for the Feedback model. Migration files allow Django to track changes to models and ensure the database remains in sync with the codebase.

To generate the migration file, run the following command:


python manage.py makemigrations

Apply the migration files to the database by running the following command:


python manage.py migrate

Test the Django CRUD REST API Project

We are now ready to test the CRUD functionalities of the API using an API testing tool like Postman. If you plan to use Postman as well, I’ve included a pre-defined collection with request URLs, data, HTTP methods, and more in a file named Feedback App.postman_collection.json, located at the root of the final project.

Simply import this JSON file into Postman to access the collection used for testing the API.

Once you have the API collection imported, start the Django development server by running the command below:


python manage.py runserver

Create Feedback

To create new feedback, add the following JSON to the request body in your API client, and send a POST request to the http://localhost:8000/api/feedbacks/ endpoint.


{
    "name": "John Doe",
    "email": "johndoe@gmail.com",
    "feedback": "Thanks CodevoWeb. I improved my Rust skills by following your Rust articles.",
    "rating": 4.5,
    "status": "active"
}

Within a few seconds, you should receive a 201 response from the API, indicating that the request was successful.

create new feedback django rest api crud

Edit Feedback

To update an existing feedback record in the database, add the updated data to the request body of your API client and send a PATCH request to http://localhost:8000/api/feedbacks/{feedback_id}.


{
    "feedback": "Rust is the best language to learn",
    "rating": 4.3,
    "name": "Edem",
    "status": "active"
}
edit feedback django rest crud api

Fetch All Feedback

To retrieve a list of feedback entries from the database, send a GET request to http://localhost:8000/api/feedbacks. The API supports pagination, so you can include page and limit parameters in the URL to control the results.

get all feedback django rest api crud

Delete Feedback

To delete a feedback entry from the database, send a DELETE request to http://localhost:8000/api/feedbacks/{feedback_id}.

delete feedback django rest api crud

Conclusion

And that’s a wrap! Congratulations on making it to the end. In this guide, you learned how to build a complete CRUD Django REST API using the Django REST Framework. I hope you found this tutorial both helpful and enjoyable. Thanks for following along!