How to implement RESTful routing in Django
Learn to leverage the REST archicture in a Django app.
Published: 19 Sep, 2024
Django URLs aren’t typically RESTful. Here are the URLs I would have for a fictional Contact manager app:
- GET /contacts/ - Retrieve all contacts.
- POST /contacts/ - Create new contact.
- POST /contacts/delete/ - Delete all contacts.
- POST /contacts/update/<int:contact_pk>/ - Update a contact.
- POST /contacts/delete/<int:contact_pk>/ - Delete a contact.
- GET /contacts/<int:contact_pk>/ - Retrieve a contact.
Then I will have views like so:
def contacts(request):
return HttpResponse("All contacts")
def create_contact(request):
return HttpResponse("Contact created")
def delete_all_contacts(request):
return HttpResponse("All contacts deleted.")
def update_contact(request, contact_pk):
return HttpResponse("Contact updated.")
# ... And so on.
Notice how I can only use the GET and POST HTTP verbs. By definition, REST requires that a resource be managed by the GET, POST, PUT, PATCH, and DELETE HTTP verbs. These routes aren’t RESTful.
That’s because traditionally, Django apps made heavy use of the HTML form element to perform any updates. And this element only supports either GET or POST. These days, most updates are done via AJAX, so it makes sense to use the REST architecture instead.
Restful URLS
I should have only two routes:
- GET | POST | DELETE /contacts/ - List all contacts, create a new one, or delete all contacts.
- GET | PUT | PATCH | DELETE /contacts/<int:contact_pk>/ - Given the primary key of a contact, retrieve it’s details, replace it with new data, update parts of the data, or delete it.
I decided to solve this problem by using verb handlers. Look at these views:
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
def handle_create(request):
return HttpResponse("Contact created.")
def handle_list(request):
return HttpResponse("A list of contacts.")
def handle_delete_all(request):
return HttpResponse("All contacts deleted.")
def handle_partial_update(request, contact_pk):
return HttpResponse(f"Contact {contact_pk} has been updated.")
def handle_replace(request, contact_pk):
return HttpResponse(f"Data in contact {contact_pk} replaced with new data.")
def handle_delete(request, contact_pk):
return HttpResponse(f"Contact {contact_pk} has been deleted.")
def handle_detail(request, contact_pk):
return HttpResponse(f"Details about contact {contact_pk}.")
@csrf_exempt
@require_http_methods(["GET", "POST", "DELETE"])
def root_view(request):
if request.method == "POST":
return handle_create(request)
elif request.method == "DELETE":
return handle_delete_all(request)
return handle_list(request)
@csrf_exempt
@require_http_methods(["GET", "PATCH", "PUT", "DELETE"])
def object_view(request, contact_pk):
if request.method == "PATCH":
return handle_partial_update(request, contact_pk)
elif request.method == "PUT":
return handle_replace(request, contact_pk)
elif request.method == "DELETE":
return handle_delete(request, contact_pk)
return handle_detail(request, contact_pk)
This file only has two views called root_view and object_view. The other functions are the verb handlers I mentioned.
Then, I simply hook the views in to the URL dispatcher like so:
from django.contrib import admin
from django.urls import path
from myapp import views
urlpatterns = [
path("contacts/", views.root_view),
path("contacts/<int:contact_pk>/", views.object_view),
]
Use curl to test the routes:
curl http://localhost:8000/contacts/
# A list of contacts.
curl -X DELETE http://localhost:8000/contacts/
# All contacts deleted.
curl -X PATCH http://localhost:8000/contacts/1/
# Contact 1 has been updated.
As you can see, these URLs are fully RESTful when compared to the previous ones.