A Practical Guide To Ajax

This is my compilation of a full explanation of Ajax from the bottom up. I wanted to put everything about Ajax in one easily-accessible place for anyone who remains confused by this confusing aspect of JS.

I will use the PHP language throughout this article as my server-side language

By the time you finish reading this post, you should be able to understand every aspect of e.g. this code:


function doSomething(jsonToJS) {
    // Do something on the current page..
}

function getAjax() {

    let id = document.getElementById("form-control");

    let req = new XMLHttpRequest();
    let fd = new FormData(id);

    req.open('post', '/path/to/ajaxification.php');
    req.send(fd);

    req.upload.addEventListener('progress', function() {
        // Do something to show the progress of the upload..
    });

    req.upload.addEventListener('error', function() {
        // Print something to show the error in the upload..
    });

    // Downloading the response from the server:
    req.addEventListener('load', function() {
        let rawResponse = req.responseText;
// console.log(rawResponse);
        let jsonToJS = JSON.parse(rawResponse);
        doSomething(jsonToJS);
// OR:
        window.location.assign('/some-location?' + 'key1=val1');
    });
}

    

Disclaimer: This is a simplified version of asynchrony etc. Any mistakes are my own alone. This page also presents only one way of doing Ajax: modern enough not to be quite as convoluted as the Ajax of old, yet not so modern that most Ajax you see in the wild will be radically different than what I present here.

AJAX stands for Asynchronous JavaScript And XML. Today it's mostly used with JSON rather than XML, but Ajax sounds better than AJAJ.

Here's an explanation of Ajax in terms of its component parts: what 'asynchronous' really means, what 'XMLHTTPRequest' and its basic Ajax functions are, and how to use Ajax at all stages of the HTTP request / response cycle.

Asynchrony

JS is a 'non-blocking, single-threaded, asynchronous language'. An asynchronous language has a set of built-in asynchronous functions, which we define below. But that's it: nothing more fancy about a language being 'asynchronous' than 'having built-in asynchronous functions'.

Traditional languages are not asynchronous, i.e., they have no built-in asynchronous functions.

Let's break down exactly what's meant by 'non-blocking', 'single-threaded', and 'asynchronous':

A Simple Example Of JS Asynchrony

Here is the a very simple way to show how synchronous functions work.

'console.log(…)' is a synchronous function.

'setTimeout(…)' is an asynchronous function:

    
console.log(1);
setTimeout(function(){
    console.log(2);}, 5000);
console.log(3);
// OUTPUT:
1
3
[hangs for 5 seconds, then:] 2
    

If 'setTimeout()' were synchronous, we'd have very different output:

    
console.log(1);
// If 'setTimeout' were synchronous:
setTimeout(function(){
    console.log(2);}, 5000);
console.log(3);
// OUTPUT:
1
[hangs for 5 seconds, then:] 2
3
    

Making an AJAX Request with JS's XMLHttpRequest Object

JS uses the XMLHTTPRequest object (the official Ajax object) to send data from the client-side to the server-side, and optionally to receive data from the server-side back to the client-side when the data it sent is finished processing.

Here are the standard functions of the XMLHttpRequest object:

    
function someFunction() {
    // Do something on this page
}

function doAjax() {
    let request = new XMLHttpRequest();

    request.open('get', '/path/to/ajaxification.php');
    request.send();

    request.addEventListener('load', function(event) {
        let rawResponse = req.responseText;
        let jsonToJS = JSON.parse(rawResponse);
        someFunction(jsonToJS);
    });
}
    

What the Methods of the XMLHttpRequest Object Mean

'/path/to/ajaxification.php': The Backend Page That You Send Your Request Off To

Every Ajax request should be processed on the server.

The server should have it's own page for all Ajax requests, separate from the webpage where Ajax is called: in this case, we've called it 'ajaxification.php'

Note that in this case its a PHP page, but the backend language you're using could be anything: Node, Java, etc.

You can see the results of ajaxification.php when the server returns its response.

ajaxification.php's response is returned to the Ajax request in its raw form as 'req.responseText'

The response from ajaxification.php should be in JSON.

If you have errors in the JSON you've output from ajaxification.php however, then req.responseText has the entire page within it, which is especially good for debugging.

Once you've debugged, you should JSON.parse(req.responseText) the JSON you got in the response. JSON.parse(req.responseText) converts the req.responseText from JSON into pure JS.

Changing The Body of POST Requests with Headers: 'request.setRequestHeader(header, value)'

The 'req.setRequestHeader(header, value)' method allows you to notify the server of the type of request you're sending to it, which is necessary for an error-free response:


// Default (usable with basic POST requests):
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// POSTing media, <form>s:
req.setRequestHeader('Content-Type', 'multipart/form-data');

    

There are a few things to be aware of when using the req.setRequestHeader(header, value) method:


let id = document.getElementById("form-control");

let req = new XMLHttpRequest();
let fd = new FormData(id);

req.open('post', 'path/to/ajaxification.php');

req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setRequestHeader('X-CSRF-TOKEN', 'dsa45hfd436hsstr46hwer56663sgf');

req.send(fd);

    

Uploads and Downloads with Ajax

What The '.addEventListener(eventTypes, …)' Mean

Both uploads and downloads through Ajax have the same event types ('.addEventListener(eventTypes, …)') for showing Ajax's progress in uploading to the server, and downloading the server's response. Here's what they mean:

Ajax At All Stages Of The HTTP Request / Response Cycle

  1. The Page Is First Painted From The Server-Side. All variables from JS scripts you include at the point when the page is first loaded up will be accessible to you on that page:
        
    <!-- In any script included here, all global-scoped JS variables are accessible: --> 
        <script src="includes/themes/mgm-default-theme/js/highlight.js"></script>
        <script src="includes/themes/mgm-default-theme/js/homepage.js"></script>
        
    
    • Before The Page Is Painted: Including PHP Vars Translated To JS Vars On The Server-Side.
          
      <?php
          $php_var = array('yes' => 'hi', 'no' => 'bye');
          $php_var = json_encode($php_var);
      ?>
      <script>
          // For first printing the page:
          let jsVar = <?php echo $php_var; ?>
          console.log(jsVar);
          // OUTPUT on console: Object { yes: "hi", no: "bye" }
      </script>
                              
                          
  2. Sending JS Vars Up To The Server: ajaxification.php.
    • GET Requests.
      
      let req = new XMLHttpRequest();
      req.open('get', 'path/to/ajaxification.php' + '?first=' + jsVar);
      req.send();
      
          
    • POST Requests.
      
      let id = document.getElementById("form-control");
      
      let req = new XMLHttpRequest();
      let fd = new FormData(id);
      
      req.open('post', 'path/to/ajaxification.php');
      
      req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      req.setRequestHeader('X-CSRF-TOKEN', 'dsa45hfd436hsstr46hwer56663sgf');
      
      req.send(fd);
                          
                      
  3. Manipulating the JS Variable in PHP's ajaxification.php. For this, you have to:
    • Decode the JS var from JSON to PHP, then
    • Manipulate the decoded PHP variable as desired, and finally
    • echo out whatever you want to send back:
      
      $json_to_PHP = json_decode($_POST['jsVar']);
      // Manipulate $json_to_PHP here..
      echo json_encode($any_PHP_var_you_like_including_json_to_PHP);
      
          
  4. Receiving The Response Back In JS.
    
    req.addEventListener('load', function(event) {
        let jsObj = JSON.parse(req.responseText);
        // jsObj is a JS Object which contains anything sent by the server:
        // jsObj.serverVar1, jsObj.serverVar2, etc.
        doSomething(jsObj);
    });
    
        
  5. Redirecting Without Involving The Server. You can use JS with the 'load' event type to cause a redirect, with no server involvement whatsoever (easing your HTTP requests):
    
    req.addEventListener('load', function(event) {
        window.location.assign('/some-location?' + 'key1=val1');
    });
                
                    

Ajax Security: Client Side, Server Side

  1. Client-Side.
    • Use official JS APIs, like the FormData object or JSON.stringify(…) (a way to convert JS data into a JSON string) for anything you send up to the server with req.send(body)
      • In other words, don't try and JSONify what you're sending yourself: JS provides APIs that take care of the edge cases for you.
      • Doing it yourself is about as wise as devising your own encryption algorithm. It's something that you probably shouldn't do if you haven't spent years studying and honing those skills, backed by a security team of others who are in the same position.
    • Avoid building JSON.stringify(withVarables). Put in literals only.
  2. Server-Side. In PHP:
    • As part of your standard MO, provide an anti-CSRF function in ajaxification.php (I won't go into how to do this here, but take a look at OWASP's site to get an idea of how to do this).
    • Use official PHP functions / language constructs to decode JSON strings from the frontend, and encode anything you're going to send back to the front end from the server-side.
    • When encoding an array, use json_encode(…) only to encode associative arrays.
      • This will ensure that the JSON returned to the frontend will be a JSON object, and not a JSON array.
      • The reason JSON arrays are dangerous: they can be manipulated by an attacker (whereas JSON objects can not).

Debugging Ajax