Q&A
How to work with AJAX requests and responses in Django
Overview
AJAX (Asynchronous JavaScript and XML - though today it's almost always JSON) enables dynamic pages: live search, form submission without reload, infinite scroll. On the Django side, you create a view that returns JsonResponse and a URL route for it; on the front end, you call that URL with fetch and handle the JSON. The notable Django-specific wrinkle is CSRF tokens on unsafe methods.
graph LR
A[Browser: fetch() to a Django URL] --> B[Django view processes request]
B --> C[Return JsonResponse(data)]
C --> D[JS receives JSON]
D --> E[Update the DOM - no reload]
style C fill:#1e1b4b,color:#fff
The Django View (returns JSON)
# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_GET, require_POST
import json
@require_GET
def search(request):
q = request.GET.get("q", "")
results = list(Product.objects.filter(name__icontains=q).values("id", "name")[:10])
return JsonResponse({"results": results})
@require_POST
def add_item(request):
data = json.loads(request.body) # parse JSON body
item = Item.objects.create(name=data["name"])
return JsonResponse({"id": item.id, "name": item.name}, status=201)
JsonResponse serializes a dict to JSON and sets the correct Content-Type. For a list at the top level, pass safe=False.
The URL
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("api/search/", views.search, name="search"),
path("api/items/", views.add_item, name="add_item"),
]
The Front-End (fetch)
GET
const res = await fetch(`/api/search/?q=${encodeURIComponent(term)}`);
const data = await res.json();
renderResults(data.results);
POST (with CSRF token)
Django blocks unsafe requests without a valid CSRF token. Include it in a header:
function getCookie(name) {
const m = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
return m ? m[2] : null;
}
const res = await fetch("/api/items/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": getCookie("csrftoken"), // REQUIRED for POST
},
body: JSON.stringify({ name: "New item" }),
});
const item = await res.json();
The CSRF token (from the csrftoken cookie) sent as the X-CSRFToken header is what satisfies Django's CSRF middleware. Forgetting it yields a 403.
Detecting AJAX vs Normal Requests
The old request.is_ajax() was removed in Django 3.1. Check the header yourself if needed:
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
... # but a cleaner approach is dedicated JSON endpoints
The modern pattern is simply to have separate JSON API endpoints rather than branching inside an HTML view.
Django REST Framework (for real APIs)
For anything beyond a couple of endpoints, Django REST Framework (DRF) is the standard: serializers, viewsets, authentication, and browsable APIs replace hand-rolled JsonResponse views. Mention DRF when the scope is a genuine API rather than a one-off AJAX call.
Why This Matters in Interviews
Django interviews use this to test the request/response cycle and JSON handling. Key points: return JsonResponse (and safe=False for lists), parse request.body for JSON POSTs, and - the Django-specific must-know - CSRF protection: unsafe methods need the X-CSRFToken header from the csrftoken cookie. Knowing is_ajax() was removed (check X-Requested-With or use dedicated endpoints) and that DRF is the tool for real APIs shows current knowledge.
Common Pitfalls
- Missing the CSRF token on POST/PUT/DELETE → 403 Forbidden.
- Returning a list to
JsonResponsewithoutsafe=False. - Using the removed
request.is_ajax()on Django 3.1+. - Forgetting to parse
request.body(it's raw bytes, notrequest.POST, for JSON payloads). - Not handling non-200 responses on the client (
res.ok).
References
- Django
JsonResponse: https://docs.djangoproject.com/en/stable/ref/request-response/#jsonresponse-objects - Django CSRF / AJAX: https://docs.djangoproject.com/en/stable/howto/csrf/