py4u guide

Creating a REST API with Python and Django

In today’s interconnected digital world, **REST APIs (Representational State Transfer Application Programming Interfaces)** have become the backbone of modern web and mobile applications. They enable seamless communication between client-side (e.g., websites, mobile apps) and server-side systems by defining standard rules for data exchange. Python, with its simplicity and robust ecosystem, is a popular choice for building APIs. When combined with **Django**—a high-level Python web framework—and **Django REST Framework (DRF)**—a powerful toolkit for building RESTful APIs—developers can create scalable, maintainable APIs in record time. This blog will guide you through the process of building a fully functional REST API using Python, Django, and DRF. We’ll cover everything from project setup to testing, authentication, and advanced features like pagination. By the end, you’ll have a production-ready API that supports CRUD (Create, Read, Update, Delete) operations, authentication, and more.

Table of Contents

  1. Prerequisites
  2. Setting Up the Project
    • 2.1 Creating a Virtual Environment
    • 2.2 Installing Dependencies
    • 2.3 Initializing a Django Project
  3. Configuring Django Settings
  4. Creating a Data Model
  5. Serializers: Converting Data to JSON
  6. Building Views with ViewSets
  7. Configuring URLs
  8. Testing the API
    • 8.1 Using the DRF Browsable API
    • 8.2 Testing with curl or Postman
  9. Adding Authentication
  10. Implementing Pagination
  11. Advanced Features (Optional)
    • 11.1 Filtering
    • 11.2 Permissions
  12. Conclusion
  13. References

Prerequisites

Before starting, ensure you have the following installed:

  • Python 3.8+: Download from python.org.
  • pip: Python’s package installer (usually included with Python).
  • Virtual Environment Tool: venv (built-in) or virtualenv (third-party).

Setting Up the Project

2.1 Creating a Virtual Environment

A virtual environment isolates project dependencies, preventing conflicts with other Python projects.

# Create a project folder  
mkdir django-rest-api && cd django-rest-api  

# Create and activate a virtual environment (Windows)  
python -m venv venv  
venv\Scripts\activate  

# For macOS/Linux  
python3 -m venv venv  
source venv/bin/activate  

You’ll know the environment is active when (venv) appears in your terminal prompt.

2.2 Installing Dependencies

Install Django and Django REST Framework (DRF) using pip:

pip install django==4.2.7 djangorestframework==3.14.0  
  • Django: The web framework that handles routing, ORM, and project structure.
  • Django REST Framework: Extends Django to simplify API development with serializers, viewsets, and authentication.

2.3 Initializing a Django Project

Create a new Django project and an app to house your API logic:

# Create a Django project (replace "myproject" with your project name)  
django-admin startproject myproject .  

# Create an app (e.g., "blog_api" for a blog-themed API)  
python manage.py startapp blog_api  
  • The . in django-admin startproject myproject . ensures the project files are created in the current directory (avoids nested folders).
  • blog_api is the app where we’ll define models, serializers, and views.

Configuring Django Settings

Django requires you to register apps and configure third-party tools in settings.py.

Open myproject/settings.py and update the INSTALLED_APPS list to include blog_api and rest_framework:

# myproject/settings.py  
INSTALLED_APPS = [  
    'django.contrib.admin',  
    'django.contrib.auth',  
    'django.contrib.contenttypes',  
    'django.contrib.sessions',  
    'django.contrib.messages',  
    'django.contrib.staticfiles',  
    'blog_api',  # Our custom app  
    'rest_framework',  # Django REST Framework  
]  

Creating a Data Model

APIs typically interact with a database. Let’s define a Post model to represent blog posts (title, content, author, etc.).

Open blog_api/models.py and add:

# blog_api/models.py  
from django.db import models  
from django.contrib.auth.models import User  

class Post(models.Model):  
    title = models.CharField(max_length=200)  
    content = models.TextField()  
    author = models.ForeignKey(User, on_delete=models.CASCADE)  # Link to Django's built-in User model  
    created_at = models.DateTimeField(auto_now_add=True)  # Auto-set on creation  
    updated_at = models.DateTimeField(auto_now=True)  # Auto-update on save  

    def __str__(self):  
        return self.title  # Show title in admin interface  

Explanation:

  • title: A short text field for the post title.
  • content: A long text field for the post body.
  • author: A foreign key linking to Django’s User model (deletes posts if the author is deleted via on_delete=models.CASCADE).
  • created_at/updated_at: Timestamps for when the post was created/updated.

Apply Migrations

Django uses migrations to sync models with the database. Run these commands to create and apply migrations:

# Generate migration files based on the model  
python manage.py makemigrations  

# Apply migrations to the database  
python manage.py migrate  

You’ll see output confirming tables were created (e.g., blog_api_post for the Post model).

Serializers: Converting Data to JSON

APIs send and receive data in formats like JSON. Serializers convert Django model instances (Python objects) to JSON (and vice versa).

Create a serializers.py file in blog_api:

# blog_api/serializers.py  
from rest_framework import serializers  
from .models import Post  

class PostSerializer(serializers.ModelSerializer):  
    class Meta:  
        model = Post  
        fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at']  
        read_only_fields = ['author', 'created_at', 'updated_at']  # Auto-set by server  

    def create(self, validated_data):  
        # Set the author to the current authenticated user  
        validated_data['author'] = self.context['request'].user  
        return super().create(validated_data)  

Key Details:

  • ModelSerializer: Automatically generates fields and validation logic from the Post model.
  • fields: Specifies which model fields to include in the API response.
  • read_only_fields: Fields that cannot be modified via the API (e.g., author is set by the server).
  • create method: Overrides the default to set the author to the authenticated user making the request.

Building Views with ViewSets

Views handle API requests (e.g., GET, POST). DRF’s ViewSets group related views (e.g., list, create, update) into a single class, reducing boilerplate.

Open blog_api/views.py and define a PostViewSet:

# blog_api/views.py  
from rest_framework import viewsets  
from rest_framework.permissions import IsAuthenticatedOrReadOnly  
from .models import Post  
from .serializers import PostSerializer  

class PostViewSet(viewsets.ModelViewSet):  
    queryset = Post.objects.all().order_by('-created_at')  # Fetch all posts, newest first  
    serializer_class = PostSerializer  
    permission_classes = [IsAuthenticatedOrReadOnly]  # Allow unauthenticated users to read only  

What is a ModelViewSet?

A ModelViewSet provides CRUD operations out of the box:

  • list(): GET /posts/ – Returns all posts.
  • retrieve(): GET /posts/{id}/ – Returns a single post.
  • create(): POST /posts/ – Creates a new post.
  • update(): PUT /posts/{id}/ – Updates an existing post.
  • partial_update(): PATCH /posts/{id}/ – Partially updates a post.
  • destroy(): DELETE /posts/{id}/ – Deletes a post.

Permissions:

IsAuthenticatedOrReadOnly ensures:

  • Authenticated users can create/update/delete posts.
  • Unauthenticated users can only view posts.

Configuring URLs

URLs map API endpoints (e.g., /api/posts/) to views. DRF’s DefaultRouter automatically generates URLs for ViewSets.

Step 1: Create API URLs

Create blog_api/urls.py to define app-specific API routes:

# blog_api/urls.py  
from django.urls import path, include  
from rest_framework.routers import DefaultRouter  
from .views import PostViewSet  

router = DefaultRouter()  
router.register(r'posts', PostViewSet)  # Registers PostViewSet at /posts/  

urlpatterns = [  
    path('', include(router.urls)),  # Includes all router-generated URLs  
]  

Include the app’s API URLs in the project’s main urls.py:

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

urlpatterns = [  
    path('admin/', admin.site.urls),  
    path('api/', include('blog_api.urls')),  # API root at /api/  
]  

Now, your API will be available at:

  • GET /api/posts/ – List all posts.
  • POST /api/posts/ – Create a post.
  • GET /api/posts/{id}/ – Retrieve a post.
  • And so on.

Testing the API

DRF provides a built-in browsable API for testing. Let’s start the development server and explore:

python manage.py runserver  

Visit http://127.0.0.1:8000/api/posts/ in your browser. You’ll see DRF’s interactive interface:

Using the Browsable API

  1. Create a Superuser: To test authenticated actions (e.g., creating posts), create a Django admin user:

    python manage.py createsuperuser  

    Follow the prompts to set a username, email, and password.

  2. Log In: Visit http://127.0.0.1:8000/api/posts/ and click “Log in” in the top-right corner. Use your superuser credentials.

  3. Create a Post: Click “POST” and fill in the title and content fields. The author will auto-populate with your user.

  4. Test Endpoints: Use the interface to send GET, PUT, or DELETE requests to test CRUD operations.

Testing with curl or Postman

For programmatic testing, use tools like curl or Postman:

Example: List Posts (GET)

curl http://127.0.0.1:8000/api/posts/  

Example: Create a Post (POST)

curl -X POST http://127.0.0.1:8000/api/posts/ \  
  -H "Content-Type: application/json" \  
  -H "Authorization: Token YOUR_TOKEN" \  
  -d '{"title":"My First Post","content":"Hello, Django REST API!"}'  

(We’ll cover authentication tokens in the next section.)

Adding Authentication

By default, anyone can create posts (if logged in via the browsable API). To secure the API for programmatic clients (e.g., mobile apps), add token authentication.

Step 1: Enable Token Authentication

Update myproject/settings.py to include DRF’s token authentication:

# myproject/settings.py  
INSTALLED_APPS += ['rest_framework.authtoken']  # Add token auth app  

REST_FRAMEWORK = {  
    'DEFAULT_AUTHENTICATION_CLASSES': [  
        'rest_framework.authentication.TokenAuthentication',  # Token-based auth  
        'rest_framework.authentication.SessionAuthentication',  # For browsable API  
    ],  
}  

Step 2: Generate Token URLs

Add token authentication endpoints to blog_api/urls.py:

# blog_api/urls.py  
from rest_framework.authtoken.views import obtain_auth_token  # Add this import  

urlpatterns = [  
    path('', include(router.urls)),  
    path('token/', obtain_auth_token, name='api_token_auth'),  # Token endpoint  
]  

Now, users can request a token by sending a POST to /api/token/ with their username and password.

Step 3: Get a Token

Use curl to request a token for your superuser:

curl -X POST http://127.0.0.1:8000/api/token/ \  
  -H "Content-Type: application/json" \  
  -d '{"username":"your_username","password":"your_password"}'  

Response:

{"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}  

Step 4: Use the Token in Requests

Include the token in the Authorization header for authenticated requests:

# Example: Create a post with a token  
curl -X POST http://127.0.0.1:8000/api/posts/ \  
  -H "Content-Type: application/json" \  
  -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" \  
  -d '{"title":"Token-Authenticated Post","content":"This post was created with a token!"}'  

Implementing Pagination

As your API grows, returning all posts at once becomes inefficient. Add pagination to limit results per page.

Update myproject/settings.py to enable pagination:

# myproject/settings.py  
REST_FRAMEWORK = {  
    # ... (previous auth settings)  
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  
    'PAGE_SIZE': 5,  # Return 5 posts per page  
}  

Now, GET /api/posts/ will return:

{  
  "count": 10,  # Total posts  
  "next": "http://127.0.0.1:8000/api/posts/?page=2",  # Next page URL  
  "previous": null,  # Previous page URL (null if first page)  
  "results": [ ... ]  # 5 posts  
}  

Advanced Features (Optional)

11.1 Filtering

Allow users to filter posts by author, title, or date using django-filter:

pip install django-filter==22.1  

Update settings.py and views.py:

# myproject/settings.py  
INSTALLED_APPS += ['django_filters']  

REST_FRAMEWORK = {  
    # ...  
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],  
}  

# blog_api/views.py  
from django_filters.rest_framework import DjangoFilterBackend  # Add import  

class PostViewSet(viewsets.ModelViewSet):  
    # ...  
    filter_backends = [DjangoFilterBackend]  
    filterset_fields = ['author__username', 'title']  # Filter by author username or title  

Now users can filter posts with:

GET /api/posts/?author__username=johndoe  
GET /api/posts/?title=Django  

11.2 Permissions

Restrict post edits/deletions to the original author using IsAuthorOrReadOnly:

# blog_api/permissions.py  
from rest_framework import permissions  

class IsAuthorOrReadOnly(permissions.BasePermission):  
    def has_object_permission(self, request, view, obj):  
        # Allow read-only requests (GET, HEAD, OPTIONS)  
        if request.method in permissions.SAFE_METHODS:  
            return True  
        # Write permissions only for the author  
        return obj.author == request.user  

Update PostViewSet in views.py:

from .permissions import IsAuthorOrReadOnly  # Add import  

class PostViewSet(viewsets.ModelViewSet):  
    # ...  
    permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]  

Conclusion

You’ve built a fully functional REST API with Django and DRF! We covered:

  • Project setup and configuration.
  • Model creation and database migrations.
  • Serializers for data conversion.
  • ViewSets for CRUD operations.
  • Authentication with tokens.
  • Pagination and advanced features like filtering.

DRF’s flexibility lets you extend this API further with permissions, throttling, or custom serializers. Explore the official docs to dive deeper!

References