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:
- CRUD Operations on PostgreSQL using Django REST Framework
- CRUD RESTful API Server with Python, FastAPI, and MongoDB
- Build a CRUD App with FastAPI and SQLAlchemy
- Build a CRUD App with FastAPI and PyMongo
- Node.js, Express, TypeORM, PostgreSQL: CRUD Rest API
- Build CRUD RESTful API Server with Golang, Gin, and MongoDB
- Next.js Full-Stack App with React Query, and GraphQL-CodeGen
- Build Full-Stack tRPC CRUD Application with Node.js, and React.js
- GraphQL CRUD API with Next.js, MongoDB, and TypeGraphQL
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 OS –
python -m venv venv
- Mac or Linux OS –
python3 -m venv venv
- Windows OS –
- After creating the virtual environment, your IDE or code editor may prompt you to activate it for the workspace folder. Select “Yes” to proceed.
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
- Windows OS (Command Prompt ) –
- 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 OS –
python -m venv venv
- Mac or Linux OS –
python3 -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.
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"
}
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.
Delete Feedback
To delete a feedback entry from the database, send a DELETE request to http://localhost:8000/api/feedbacks/{feedback_id}
.
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!