Building a CRUD API with Django and PostgreSQL
In this post, we’ll explore how to create a CRUD (Create, Read, Update, Delete) API using Django models and integration with PostgreSQL, a powerful relational database. To further enhance our development experience, we’ll utilize Docker Compose, a container orchestration tool that facilitates the creation and management of isolated and reproducible development environments.
1. Implementation
Before we begin, make sure you have the Python installed. You may visit https://www.python.org/downloads and download the latest version of Python. Once the Python is installed, open a terminal and run the following command to verify:
python -v
1.1 Setup Virtualenv
Virtualenv is a tool that creates isolated Python environments. This is useful for development. To install virtualenv
, run the following commands in your terminal:
mkdir django_postgres_api
cd django_postgres_api
pip3 install virtualenv
virtualenv env
source env/bin/activate
pip3 install --upgrade pip
1.2 Setup Django Project and App
Start by creating a new Django project and a Django app to encapsulate the API:
pip3 install django djangorestframework
django-admin startproject backend
cd backend
python3 manage.py startapp api
Now that the api
application has been created, we need to configure it in the INSTALLED_APPS
in. the settings.py
. This will enable the app features such as the app’s models, views, forms, or other components to be available in the Django project.
To verify that Django has been set up successfully, run the following commands in your terminal to start the Django project:
python3 manage.py runserver
Now, open your browser and navigate to the URL http://127.0.0.1:8000
. If you see the Django welcome page, then Django has been set up successfully.
We are ready to move forward to the API building.
2. Setup PostgreSQL Database
When using PostgreSQL with Django, it’s necessary to install the psycopg2-binary
package, which is a PostgreSQL adapter for Python. This package allows Django to interact with a PostgreSQL database.
pip3 install psycopg2-binary
Configure the Django project to use PostgreSQL by updating the DATABASES
settings in the settings.py
file:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": os.getenv("DJANGO_DB_NAME", "django_api_db"),
"USER": os.getenv("DJANGO_DB_USER", "postgres"),
"PASSWORD": os.getenv("DJANGO_DB_PASSWORD", "123456"),
"HOST": os.getenv("DJANGO_DB_HOST", "localhost"),
"PORT": os.getenv("DJANGO_DB_PORT", "localhost")
}
}
Apply the initial migrations to create the necessary database tables:
python3 manage.py makemigrations
python3 manage.py migrate
Migrations are files generated by Django to track changes to our models over time. When we create a new model or modify an existing one, Django generates migration files that describe the changes needed to apply those modifications to the database schema. Django maintains a special table django_migrations
in the database to keep track of which migrations have been applied.
Django provides an administrative interface (django.contrib.admin
) that allows us to manage our models and data through a web-based interface. When you run migrations, Django creates some additional tables to support the admin functionality. These tables include but are not limited to, tables for user authentication auth_user
, auth_group
, et and Django admin logs django_admin_log
.
3. Define Models
Define the models for orders and products in the api/models.py
file:
class Product(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255)
description = models.TextField()
price = models.FloatField()
store_id = models.CharField(max_length=255)
stock = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Order(models.Model):
id = models.AutoField(primary_key=True)
order_dt = models.DateTimeField(auto_now_add=True)
product_id = models.IntegerField()
quantity = models.IntegerField()
status_choices = [
('CREATED', 'Created'),
('PAID', 'Paid'),
('SHIPPED', 'Shipped'),
('DELIVERED', 'Delivered'),
('CANCELLED i', 'Cancelled'),
]
status = models.CharField(max_length=10, choices=status_choices, default='CREATED')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Run the python3 manage.py makemigrations
generates new migration files in the api/migrations/
directory. Followed by running the python3 manage.py migrate
to apply the new order
and product
model creation.
Now verify the two new tables created in PostgreSQL:
4. Create Serializers
A serializer is a class that is used to convert Django models to and from JSON format. In the api/serializers.py
file, create serializers for the models:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = '__all__'
5. Implements Views
In the Django REST framework
, views play a crucial role in handling user requests and generating responses. Views are Python functions or classes that receive a web request, process the request, and return a web response. Django offers several types of views to cater to different use cases and preferences.
5.1 APIView
APIView
classes extend Django’s View
class. These views are specifically designed for handling API requests and responses. It provides methods like get()
, post()
, put()
, etc., that we can override to define the behavior for specific HTTP methods. Use APIView
when you need full control over the implementation of each HTTP method.
class MyCustomView(APIView):
def get(self, request):
# Custom logic for handling GET requests
return Response({"message": "Custom GET response"})
def post(self, request):
# Custom logic for handling POST requests
return Response({"message": "Custom POST response"})
5.2 ModelViewSet
ModelViewSet
is a specialized view for handling CRUD operations on Django models. It combines the functionality of several generic views to provide a comprehensive API for a model. Use ModelViewSet
when building a RESTful API for Django models.
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
This setup will automatically generate the appropriate URL patterns for the actions provided by ModelViewSet
. For example:
GET /mymodels/
: List all instances.POST /mymodels/
: Create a new instance.GET /mymodels/{pk}/
: Retrieve a specific instance.PUT /mymodels/{pk}/
: Update a specific instance.PATCH /mymodels/{pk}/
: Partially update a specific instance.DELETE /mymodels/{pk}/
: Delete a specific instance.
In the api/views.py
file, create views using Django Rest Framework’s ModelViewSet
:
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
6. Configure URLs
In the api/urls.py
file, configure the URLs for the API:
router = DefaultRouter()
router.register(r'products', ProductViewSet)
router.register(r'orders', OrderViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Include the API URLs in the main project’s urls.py
file:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('api.urls')),
]
7. Run the Development Server
Start the Django development server and navigate to http://localhost:8000/api/products/
to test the CRUD operations for products.
Let’s insert a product record.
Let’s try to retrieve this product record.
8. Set Up Docker Compose
We will be utilizing Docker Compose to ensure a clean and isolated development environment. Before we begin, make sure we have the following installed:
- Docker
- Docker Compose
8.1 Create Entrypoint Script
We want to run migrations as part of the command when starting the Django application container and ensure that the database is up before running migrations. Create a script named entrypoint.sh
in the Django project directory. This script will wait for the PostgreSQL container to be ready before running the migrations and starting the Django development server.
#!/bin/bash
set -e
# Wait for the PostgreSQL container to be ready
until pg_isready -h db -p 5432 -q -U postgres; do
echo "Waiting for the PostgreSQL container to be ready..."
sleep 2
done
# Run migrations
python manage.py makemigrations
python manage.py migrate
# Start the Django development server
exec "$@"
8.2 Create Dockerfile
In the Django project directory, we would need to create a Dockerfile
for building the Docker image.
# Use an official Python runtime as a parent image
FROM python:3.9
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Install PostgreSQL client tools
RUN apt-get update && \
apt-get install -y postgresql-client
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy just the requirements file first to leverage Docker caching
COPY requirements.txt .
# Install dependencies
RUN pip install --upgrade pip && pip install -r requirements.txt
# Copy the entrypoint script
COPY entrypoint.sh .
# Set the script as executable
RUN chmod +x entrypoint.sh
# Copy the rest of the application code
COPY . .
# Expose the port that Django will run on
EXPOSE 8000
8.3 Create Docker Compose File
Now, let’s proceed to set up the docker-compose.yml
file.
version: '3'
services:
db:
image: postgres:latest
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: mydatabaseuser
POSTGRES_PASSWORD: mysecretpassword
ports:
- "5433:5432"
volumes:
- pgdata:/var/lib/postgresql/data/
networks:
- mynetwork
api:
build: ./backend
entrypoint: /usr/src/app/entrypoint.sh
command: python manage.py runserver 0.0.0.0:8000
volumes:
- backend:/usr/src/app
ports:
- "8000:8000"
depends_on:
- db
environment:
- DEBUG=True
- DJANGO_DB_HOST=db
- DJANGO_DB_PORT=5432
- DJANGO_DB_NAME=mydatabase
- DJANGO_DB_USER=mydatabaseuser
- DJANGO_DB_PASSWORD=mysecretpassword
networks:
- mynetwork
volumes:
backend:
pgdata:
networks:
mynetwork:
8.4 Run Docker Compose
Now, with these configurations, we can run our Django application using Docker Compose:
docker-compose up --build
This will build the Docker image, start the containers, and run our Django application.
9. Conclusion
We have successfully built a CRUD API with Django and PostgreSQL for managing orders and products. This example provides a solid foundation for expanding and customizing the API to meet the specific needs of our application. In the next tutorial, we will be going to enhance it with features such as authentication, authorisation and validation in Django. Full source code is available on GitHub.
Share this content:
Leave a Comment