Building a CRUD API with Django and PostgreSQL

Building a CRUD API with Django and PostgreSQL

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
Screenshot-2023-11-22-at-4.19.32-PM Building a CRUD API with Django and PostgreSQL

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.

Screenshot-2023-11-22-at-4.58.55-PM Building a CRUD API with Django and PostgreSQL

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.

Screenshot-2023-11-22-at-4.32.58-PM Building a CRUD API with Django and PostgreSQL

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
Screenshot-2023-11-22-at-5.45.18-PM Building a CRUD API with Django and PostgreSQL

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.

Screenshot-2023-11-22-at-6.08.51-PM Building a CRUD API with Django and PostgreSQL

Now verify the two new tables created in PostgreSQL:

Screenshot-2023-11-22-at-5.44.32-PM Building a CRUD API with Django and 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.

Screenshot-2023-11-23-at-8.50.42-AM Building a CRUD API with Django and PostgreSQL

Let’s try to retrieve this product record.

Screenshot-2023-11-23-at-8.51.55-AM Building a CRUD API with Django and PostgreSQL

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.

Screenshot-2023-11-23-at-11.53.29-AM Building a CRUD API with Django and PostgreSQL

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

Discover more from nnyw@tech

Subscribe now to keep reading and get access to the full archive.

Continue reading