RESTful APIs with Django and the Django Rest Framework

RESTful APIs with Django and the Django Rest Framework

Part 1 - Manual Endpoint Construction: Understanding the Basics

In this three-part tutorial, I will provide an overview of building RESTful APIs first with just Django, guiding you through the process of constructing a basic API within a Django project. You'll learn how to define views, manage URLs, and handle requests to produce JSON responses.

Later in the series, we'll delve deeper into Django Rest Framework (DRF), an immensely powerful toolkit for building APIs in Django. DRF streamlines the development process by offering robust features such as serialization, authentication, and viewsets. Its rich ecosystem and comprehensive documentation make it an indispensable tool for developers aiming to build scalable and maintainable APIs with Django.


A RESTful API (Representational State Transfer) is a software architectural style that defines a set of constraints to be used for creating web services. These constraints emphasise simplicity, scalability, and uniformity in the communication between clients and servers over the internet.

RESTful APIs are useful because they provide a standardised way for different systems to communicate with each other, regardless of the programming languages or platforms they are built on. They allow developers to create interoperable services that can be easily integrated into various applications and ecosystems. Additionally, RESTful APIs facilitate the separation of concerns between client and server, making it easier to maintain and scale complex systems.

A RESTful API is like a set of rules or guidelines that both the client and server follow when they talk to each other over the internet. These rules make sure that the communication between them is easy, reliable, and consistent.

— in simple terms, a RESTful API is like a rulebook that helps your devices talk to each other easily and reliably over the internet.

In this tutorial, I'll start by building a basic Django Rest API and then introduce the Django Rest Framework for more efficient development.

Getting Started

First lets create a new directory called drf and inside it create a new virtual environment. I am using MacOS Sonoma, if you are on a windows or linux machine you will need to check the documentation to see how to do this for your own environment.

mkdir drf && cd drf
python3.12 -m venv venv
source venv/bin/activate

After our virtual environment is activated, if we run a pip freeze command, nothing should be visible yet, as we have not installed any packages.

It's a good idea to also check you have the latest version of pip!
pip install --upgrade pip

Inside the root folder , lets create a requirements.txt file and put in some of the packages we will be using for this tutorial.

django>=4.2.0,<4.3.0
django-cors-headers==4.3.1
djangorestframework==3.15.1
PyYAML==6.0.1
requests==2.31.0

With django>=4.2.0,<4.3.0, our project will use any version of Django from 4.2.0 up to, but not including, 4.3.0, effectively limiting it to the 4.2.x series, which is the Long Term Support release of Django.

After creating the requirements.txt file run the command
pip install -r requirements.txt to install our packages.

In our application we will need to create a directory called backend where we will install our Django and DRF and a directory called py_consumer where we will write client side code to consume the RESTFul APIs:

Inside the backend directory we will setup our Django project, so cd into the backend directory and start a new Django project. I will call this django_rest.

Notice the . at the end of the command specifies the directory in which the Django project should be created. In this context, the . refers to the current directory. This tells Django to create the "django_rest" project in the current directory. The . serves as a shorthand for the current directory. Without it, Django would create a new directory named "django_rest" and place the project files inside that directory!

> cd backend
> django-admin startproject django_rest .

After the command is called you should see the following folders created for our Django app:

Now we have created our Django project, we can build out first python API client.

Creating our Python API Client

To play around with some RESTFul endpoints, we can use the HTTPBin website.

HTTPBin provides HTTP request and response testing. It offers a variety of endpoints that allow you to simulate different HTTP methods, headers, and status codes.

HTTPBin is commonly used by developers to test and debug their HTTP client code or to troubleshoot network-related issues. It's a handy tool for quickly checking how your application behaves under different HTTP scenarios without needing to set up your own server or mock endpoints.

So lets try and test one of the endpoints provided by HTTPBin. Inside our py_consumer directory, lets create a new python file and call it test.py. In this file we are going to create some simple endpoint using some of the available URL paths from the HTTPBin site.

In a Django REST application, endpoints refer to the URLs that are exposed by the application's API to perform various operations. Each endpoint corresponds to a specific resource or functionality provided by the API.

Endpoints in a Django REST application typically follow the RESTful principles, which means they represent different CRUD (Create, Read, Update, Delete) operations on resources.

To make the HTTP requests, we will also be using the python Http requests library, so make sure you import this in your test.py file.

Below is a simple Python code snippet using the requests library to interact with HTTPBin endpoints:

import requests

# HTTPBin base URL
BASE_URL = 'https://httpbin.org'

# Example GET request to the /get endpoint
def get_example():
    url = f'{BASE_URL}/get'
    response = requests.get(url)
    print(response.json())

# Example POST request to the /post endpoint with JSON data
def post_example():
    url = f'{BASE_URL}/post'
    data = {'key': 'value'}
    response = requests.post(url, json=data)
    print(response.json())

# Example PUT request to the /put endpoint with form data
def put_example():
    url = f'{BASE_URL}/put'
    data = {'key': 'value'}
    response = requests.put(url, data=data)
    print(response.json())

# Example DELETE request to the /delete endpoint
def delete_example():
    url = f'{BASE_URL}/delete'
    response = requests.delete(url)
    print(response.json())

# Example PATCH request to the /patch endpoint
def patch_example():
    url = f'{BASE_URL}/patch'
    data = {'key': 'value'}
    response = requests.patch(url, json=data)
    print(response.json())

# Example request with custom headers
def custom_headers_example():
    url = f'{BASE_URL}/headers'
    headers = {'User-Agent': 'MyApp/1.0'}
    response = requests.get(url, headers=headers)
    print(response.json())

# Example request with query parameters
def query_params_example():
    url = f'{BASE_URL}/get'
    params = {'key': 'value'}
    response = requests.get(url, params=params)
    print(response.json())

# Example GET request to the /status/{code} endpoint
def get_status_code_example(status_code):
    url = f'{BASE_URL}/status/{status_code}'
    response = requests.get(url)
    print('\n======== Query Status Code Example ========\n')
    print(f'Status Code: {response.status_code}')
    print(response.text)

# Run the examples
if __name__ == '__main__':
    get_example()
    post_example()
    put_example()
    delete_example()
    patch_example()
    custom_headers_example()
    query_params_example()
    #status code endpoints
    get_status_code_example(200)  # Example of getting status code 200
    get_status_code_example(404)  # Example of getting status code 404
    get_status_code_example(500)  # Example of getting status code 500

The code above demonstrates various HTTP methods (GET, POST, PUT, DELETE, PATCH) along with usage of query parameters, JSON data, and custom headers and HTTP response status codes. You can run these examples to interact with different HTTPBin endpoints and see the responses.

To run the script make sure you are in py_consumer directory and run python3test.py .

The test.py script above demonstrates how to consume the HTTPBin API by making various HTTP requests to its endpoints. Each function in the script corresponds to a different HTTP method (GET, POST, PUT, DELETE, PATCH) and demonstrates how to interact with different HTTPBin endpoints to perform various actions.

By making requests to different HTTPBin endpoints with various HTTP methods and parameters, you can simulate different scenarios and observe how HTTPBin processes these requests and responds accordingly. This helps you understand how to interact with RESTful APIs and how to handle different types of requests and responses in your applications.


Here's a simple explanation of the common RESTful HTTP methods:

  1. GET: Think of it like asking for information. When you send a GET request to a server, you're asking it to give you data. It's like browsing a website to read articles or view pictures.

  2. POST: This is like submitting a form. When you send a POST request to a server, you're giving it some data and asking it to do something with that data, like creating a new user account or submitting a comment on a blog post.

  3. PUT: This is like updating something. When you send a PUT request to a server, you're asking it to update or or replace an existing resource with the data you provide. It's like editing a document and saving your changes.

  4. PATCH: Similar to PUT, but more specific. With PATCH, you're asking the server to update only certain parts of an existing resource, rather than replacing the entire resource. It's like making small edits to a document instead of rewriting the whole thing.

  5. DELETE: As the name suggests, this is for deleting something. When you send a DELETE request to a server, you're asking it to remove a resource or data. It's like throwing away a piece of paper you no longer need.

These methods help maintain a consistent and predictable way for clients (like web browsers or mobile apps) to interact with servers, making it easier to build and maintain web applications.


Hooking our python client to our Django project

So far we have not been using Django at all! Our test.py was simply using the requests library to make HTTP requests to the HTTPBin website. Now lets create a client.py file and write code that makes HTTP requests to our Django project's endpoints using the requests library.

Inside our py_consumer directory, lets create a new python file and call it client.py. In this file we are going to write the Python code that makes HTTP requests to our Django project's endpoints using the requests library:

# Make sure your Django server is up and running before 
#running this script.
import requests

# Define the URL of the Django project's endpoint.
# For best practice, it's recommended to use a specific 
#endpoint URL like
#'http://localhost:8000/api/someendpoint/'
# Adjust the endpoint URL as needed.
BASE_URL = 'http://localhost:8000/'

# Make a GET request to the endpoint.
response = requests.get(BASE_URL)

# Check if the request was successful (status code 200).
if response.status_code == 200:
    # Print the response data if the request was successful.
    # In this case, we're printing the HTML content of the response.
    print(f'Connected status code is: {response.text}')
else:
    # Print an error message if the request was not successful.
    # This could be due to various reasons such as server error or connection issues.
    print(f'Error: {response.status_code}')

The above code demonstrates how to use the requests library to interact with a Django project's endpoint by making a GET request and handling the response accordingly.

Make sure that the Django server is up and running first by
using pythonmanage.pyrunserver 8000

Creating our first RESTful API

In the above example, after hooking our client script to Django and making a request to the endpoint, we are getting back some HTML. This is not RESTFul.

A RESTful API is designed to follow the principles of REST (Representational State Transfer). These principles include using standard HTTP methods (GET, POST, PUT, DELETE, etc.) to perform operations on resources, using resource identifiers (URLs) to represent resources, and returning representations of resources (such as JSON or XML) in the response.

So in our Django app we need to create an api view to actually return some JSON. So lets create a new app in Django called api where we will build our RESTFul api views.

In the backend folder, make sure the Django server has been stopped. Then create the api app using the Django startapp command:

python manage.py startapp api

After running the command, Django will create a new directory named "api" containing the files and directories for our new app.

We can now start defining our models, views, and other components specific to our API within the "api" app directory. Don't forget to add it to the list of installed apps in your Django project's settings.py file:

Here's a simple example of how we can create a basic API view in Django. First, define a view function in the views.py file within the 'api' app directory:

from django.http import JsonResponse

def hello_world(request):
    """
    A simple API view that returns a JSON response with a greeting message.
    """
    data = {
        'message': 'Hello, world! - This is your first Django API response. '
    }
    return JsonResponse(data)

Next, define a URL pattern for this view in a urls.py file within the 'api' app directory:

from django.urls import path
from .views import hello_world

urlpatterns = [
    path('', hello_world, name='hello_world'),
]

Finally, we need to hook up the URLs defined in our 'api' app with the main urls.py file of your Django project, we need to include the URLs of our 'api' app in the urlpatterns list of the mainurls.py file.

  1. Open the urls.py file in the main directory of your Django project (usually located at the same level as the settings.py file).

  2. Import the include function from django.urls module.

  3. Add a path to include the URLs of your 'api' app. Use the include function to include the URLs defined in the urls.py file of your 'api' app.

"""
URL configuration for django_rest project.
"""
from django.contrib import admin
from django.urls import path, include # Import include function

urlpatterns = [
    path('admin/', admin.site.urls),
    # Include the URLs of the 'api' app
    path('api/', include('api.urls')),
]

This approach is considered a best practice because it allows you to organize your URL patterns into modular components (apps) and keep your main urls.py file clean and manageable. It also makes it easier to maintain and extend your project as it grows.

Now that we have created our first test endpoint, lets update our client.py script to point to it. So change the BASE_URL variable as follows:

# Base URL
BASE_URL = 'http://localhost:8000/api'

We can start the Django server again and navigate to our endpoints url, which is http://127.0.0.1:8000/api/

You should see the JSON being displayed:

Finally, lets update our client.py file to see the endpoint we have implemented and view our raw data:

import requests

# Base URL
BASE_URL = 'http://localhost:8000/api'

# Make a GET request to the endpoint
response = requests.get(BASE_URL)

# Check if the request was successful (status code 200)
if response.status_code == 200:
    # Print the response data
    print('\n======== Status Code ========\n')
    print(f'Connected status code is: {response.status_code}')
    print('\n======== JSON ========\n')
    print(f'JSON is: {response.json()}')
    print('\n======== message ========\n')
    print(f'message is: {response.json()["message"]}')
else:
    # Print an error message
    print(f'Error: {response.status_code}')

In the above example I demonstrated a simple interaction between a Django API view and a client script. The example showcases a basic communication pattern between a Django API and a client using the requests library in Python.

  1. API View (Django):

    • Defines a view function named hello_world that takes a request as input.

    • Creates a Python dictionary data containing a single key-value pair with a greeting message.

    • Returns a JSON response using the JsonResponse class, passing the data dictionary as an argument.

Client Script:

  • Imports the requests module for making HTTP requests.

  • Defines the BASE_URL variable pointing to the base URL of the Django API.

  • Makes a GET request to the API endpoint using the requests.get() function, with the full URL constructed from the BASE_URL.

  • Checks if the request was successful (status code 200).

  • If the request was successful, prints the status code, the JSON response, and extracts and prints the 'message' field from the JSON response.

The interaction between the API view and the client script demonstrates the following:

  • The API view returns a JSON response with a greeting message when accessed.

  • The client script sends a GET request to the API endpoint and processes the response, printing the status code and the message contained in the JSON response.


In Part 2 of this tutorial, we'll dive deeper into building RESTful endpoints by leveraging Django models. Models represent the structure and behaviour of data in our application. We'll explore how to define models to represent our data schema and then create RESTful endpoints that allow us to perform various operations on these models. By the end of this part, you'll have a clear understanding of how to design and implement RESTful APIs that interact with your Django models, providing a powerful interface for managing data within your application."


Resources