AlgoInsight

Q&A

How to work with AJAX requests and responses in Django

By AlgoInsight Team2026-06-07backenddjangooperating-systemssecurityjavascriptpython

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

References

More answers