Working with AJAX in Django

Last updated August 24th, 2021

AJAX, which stands for asynchronous JavaScript and XML, is a set of technologies used on the client-side to send and retrieve data from the server asynchronously.

AJAX allow us to carry out changes to the content of a web page, without requiring a reload to the entire page by the user. This can be useful, for example, for auto-completion in a search bar or form validation. Used correctly, you can improve your site's performance, decrease server load, and improve the overall user experience.

In this article, we'll look at examples of how to perform GET, POST, PUT, and DELETE AJAX requests in Django. While the focus will be on the Fetch API, we'll also show jQuery examples.

Contents

What is AJAX?

AJAX is a programming practice that uses the XMLHttpRequest (XHR) object to communicate with a server asynchronously and build dynamic webpages. Although AJAX and XMLHttpRequest are often used interchangeably, they are different things.

To send and receive data to and from a web server, AJAX uses the following steps:

  1. Create an XMLHttpRequest object.
  2. Use the XMLHttpRequest object to exchange data asynchronously between the client and the server.
  3. Use JavaScript and the DOM to process the data.

AJAX can be used with jQuery by using the ajax method, but the native Fetch API is much better since it has a clean interface and doesn't require a third-party library.

The general structure of the Fetch API looks like this:

fetch('http://some_url.com')
.then(response => response.json()) // converts the response to JSON
.then(data => {
  console.log(data);
  // do something (like update the DOM with the data)
});

Refer to Using Fetch and WindowOrWorkerGlobalScope.fetch() from the MDN Documentation for more examples along with the full options available for the fetch method.

When Should You Use AJAX?

Again, AJAX can help improve your site's performance while decreasing server load and improving the overall user experience. That said, it adds a lot of complexity to your application. Because of this, unless you're using a Single-page Application (SPA) -- like React, Angular, or Vue -- you should really only use AJAX when it's absolutely necessary.

Some examples of when you may want to think about using AJAX:

  1. Search auto-completion
  2. Form validation
  3. Table sorting and filtering
  4. Captchas
  5. Surveys and polls

In general, if the content needs to be updated a lot based on user interactions, you may want to look at using AJAX to manage updating parts of the web page rather than the entire page with a page refresh.

CRUD Resource

The examples in this article can be applied to any CRUD resource. The example Django project uses todos as it's resource:

Method URL Description
GET /todos/ Returns all todos
POST /todos/ Adds a todo
PUT /todos/<todo-id>/ Updates a todo
DELETE /todos/<todo-id>/ Deletes a todo

The example project can be found on GitHub:

  1. Fetch version: https://github.com/testdrivenio/django-ajax-xhr
  2. jQuery version: https://github.com/testdrivenio/django-ajax-xhr/tree/jquery

GET Request

Let's start with a simple GET request for fetching data.

Fetch API

Example:

fetch(url, {
  method: "GET",
  headers: {
    "X-Requested-With": "XMLHttpRequest",
  }
})
.then(response => response.json())
.then(data => {
  console.log(data);
});

The only required argument is the URL for the resource you wish to fetch the data from. If the URL requires keyword arguments or query strings, you can use Django's {% url %} tag.

Did you notice the X-Requested-With header? This is necessary to inform the server that you are sending an AJAX request.

fetch returns a promise containing the HTTP response. We used the .then method to first extract the data in JSON format from the response (via response.json()) and then to access the data returned. In the example above, we just outputted the data in the console.

https://github.com/testdrivenio/django-ajax-xhr/blob/main/static/main.js#L19-L39

jQuery AJAX

Equivalent jQuery code:

$.ajax({
  url: url,
  type: "GET",
  dataType: "json",
  success: (data) => {
    console.log(data);
  },
  error: (error) => {
    console.log(error);
  }
});

https://github.com/testdrivenio/django-ajax-xhr/blob/jquery/static/main.js#L19-L41

Django View

On the Django side of things, while there are several ways to handle AJAX requests in the views, the simplest is with a function-based view:

from django.http import HttpResponseBadRequest, JsonResponse

from todos.models import Todo


def todos(request):
    # request.is_ajax() is deprecated since django 3.1
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'

    if is_ajax:
        if request.method == 'GET':
            todos = list(Todo.objects.all().values())
            return JsonResponse({'context': todos})
        return JsonResponse({'status': 'Invalid request'}, status=400)
    else:
        return HttpResponseBadRequest('Invalid request')

In this example, our resource is todos. So, before getting the todos from the database, we verified that we're dealing with an AJAX request and that the request method is GET. If both are true, we serialized the data and sent a response using the JsonResponse class. Since a QuerySet object is not JSON serializable (the todos, in this case), we used the values method to return our QuerySet as a dictionary and then wrapped it in a list. The final result is a list of dicts.

https://github.com/testdrivenio/django-ajax-xhr/blob/main/todos/views.py#L13-L28

POST Request

Next, let's see how to handle POST requests.

Fetch API

Example:

fetch(url, {
  method: "POST",
  credentials: "same-origin",
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "X-CSRFToken": getCookie("csrftoken"),
  },
  body: JSON.stringify({payload: "data to send"})
})
.then(response => response.json())
.then(data => {
  console.log(data);
});

We need to specify how we want to send the credentials in the request.

In the above code, we used the value of "same-origin" (the default) to indicate to the browser to send the credentials if the requested URL is on the same origin as the fetch call.

In the case where the frontend and the backend are hosted on different servers, you'd have to set credentials to "include" (which always sends the credentials with each request) and enable Cross-Origin Resource Sharing in the backend. You can use the django-cors-headers package to add CORS headers to responses in a Django app.

Want to learn more about how to handle AJAX requests on the same domain and cross domain? Review the Django Session-based Auth for Single Page Apps article.

This time we sent the data to the server in the body of the request.

Take note of the X-CSRFToken header. Without it, you'd get a 403 forbidden response from the server in the terminal:

Forbidden (CSRF token missing or incorrect.): /todos/

That's because it's mandatory to include the CSRF token when making a POST request to prevent Cross Site Request Forgery attacks.

We can include the CSRF token by setting the X-CSRFToken header on each XMLHttpRequest to the value of the CSRF token.

The Django documentation simplifies our lives by providing us a nice function that allow us to acquire the token:

function getCookie(name) {
  let cookieValue = null;
  if (document.cookie && document.cookie !== "") {
    const cookies = document.cookie.split(";");
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i].trim();
      // Does this cookie string begin with the name we want?
      if (cookie.substring(0, name.length + 1) === (name + "=")) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}

https://github.com/testdrivenio/django-ajax-xhr/blob/main/static/main.js#L42-L56

jQuery AJAX

The AJAX POST request with jQuery is quite similar to the GET request:

$.ajax({
  url: url,
  type: "POST",
  dataType: "json",
  data: JSON.stringify({payload: payload,}),
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "X-CSRFToken": getCookie("csrftoken"),  // don't forget to include the 'getCookie' function
  },
  success: (data) => {
    console.log(data);
  },
  error: (error) => {
    console.log(error);
  }
});

https://github.com/testdrivenio/django-ajax-xhr/blob/jquery/static/main.js#L44-L61

Django View

On the server side, the view needs to get the data from the request in JSON format, so you'll need to use the json module in order to load it.

import json

from django.http import HttpResponseBadRequest, JsonResponse

from todos.models import Todo


def todos(request):
    # request.is_ajax() is deprecated since django 3.1
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'

    if is_ajax:
        if request.method == 'POST':
            data = json.load(request)
            todo = data.get('payload')
            Todo.objects.create(task=todo['task'], completed=todo['completed'])
            return JsonResponse({'status': 'Todo added!'})
        return JsonResponse({'status': 'Invalid request'}, status=400)
    else:
        return HttpResponseBadRequest('Invalid request')

After verifying that we're dealing with an AJAX request and that the request method is POST, we deserialized the request object and extracted the payload object. We then created a new todo and sent back the appropriate response.

https://github.com/testdrivenio/django-ajax-xhr/blob/main/todos/views.py#L13-L28

PUT Request

Fetch API

Example:

fetch(url, {
  method: "PUT",
  credentials: "same-origin",
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "X-CSRFToken": getCookie("csrftoken"),  // don't forget to include the 'getCookie' function
  },
  body: JSON.stringify({payload: "data to send"})
})
.then(response => response.json())
.then(data => {
  console.log(data);
});

This should look similar to a POST request. The only difference is the shape of the URL:

  1. POST - /todos/
  2. PUT - /todos/<todo-id>/

https://github.com/testdrivenio/django-ajax-xhr/blob/main/static/main.js#L59-L73

jQuery AJAX

Equivalent jQuery:

$.ajax({
  url: url,
  type: "PUT",
  dataType: "json",
  data: JSON.stringify({payload: payload,}),
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "X-CSRFToken": getCookie("csrftoken"),  // don't forget to include the 'getCookie' function
  },
  success: (data) => {
    console.log(data);
  },
  error: (error) => {
    console.log(error);
  }
});

https://github.com/testdrivenio/django-ajax-xhr/blob/jquery/static/main.js#L64-L81

Django View

Example:

import json

from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404

from todos.models import Todo


def todo(request, todoId):
    # request.is_ajax() is deprecated since django 3.1
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'

    if is_ajax:
        todo = get_object_or_404(Todo, id=todoId)

        if request.method == 'PUT':
            data = json.load(request)
            updated_values = data.get('payload')

            todo.task = updated_values['task']
            todo.completed = updated_values['completed']
            todo.save()

            return JsonResponse({'status': 'Todo updated!'})
        return JsonResponse({'status': 'Invalid request'}, status=400)
    else:
        return HttpResponseBadRequest('Invalid request')

https://github.com/testdrivenio/django-ajax-xhr/blob/main/todos/views.py#L31-L53

DELETE Request

Fetch API

Example:

fetch(url, {
  method: "DELETE",
  credentials: "same-origin",
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "X-CSRFToken": getCookie("csrftoken"),  // don't forget to include the 'getCookie' function
  }
})
.then(response => response.json())
.then(data => {
  console.log(data);
});

https://github.com/testdrivenio/django-ajax-xhr/blob/main/static/main.js#L76-L89

jQuery AJAX

jQuery code:

$.ajax({
  url: url,
  type: "DELETE",
  dataType: "json",
  headers: {
    "X-Requested-With": "XMLHttpRequest",
    "X-CSRFToken": getCookie("csrftoken"),
  },
  success: (data) => {
    console.log(data);
  },
  error: (error) => {
    console.log(error);
  }
});

https://github.com/testdrivenio/django-ajax-xhr/blob/jquery/static/main.js#L84-L100

Django View

View:

from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404

from todos.models import Todo


def todo(request, todoId):
    # request.is_ajax() is deprecated since django 3.1
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'

    if is_ajax:
        todo = get_object_or_404(Todo, id=todoId)

        if request.method == 'DELETE':
            todo.delete()
            return JsonResponse({'status': 'Todo deleted!'})
        return JsonResponse({'status': 'Invalid request'}, status=400)
    else:
        return HttpResponseBadRequest('Invalid request')

https://github.com/testdrivenio/django-ajax-xhr/blob/main/todos/views.py#L31-L53

Summary

AJAX allows us to perform asynchronous requests to change parts of a page without having to reload the entire page.

In this article, you saw, in detail, examples of how to perform GET, POST, PUT, and DELETE AJAX requests in Django with the Fetch API and jQuery.

The example project can be found on GitHub:

  1. Fetch version: https://github.com/testdrivenio/django-ajax-xhr
  2. jQuery version: https://github.com/testdrivenio/django-ajax-xhr/tree/jquery
Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.