Beyond admin-ajax, using the WordPress REST API

Typically admin-ajax has been the ‘go to’ for handling asynchronous requests within WordPress. Since WordPress 4.4 we now have the ability to use the WP REST API to handle such requests. The new WP REST API introduced in 4.4 allows us to create structured RESTful interfaces for these requests and removes a lot of the overhead that comes with loading admin-ajax.

I’d like to illustrate the differences between the approaches with a code comparison. For this example, I’ll be creating a simple form which validates an email address using the WordPress is_email function. Don’t forget to read the comments in the code as I’m primarily using this to show where the approaches differ.

I won’t be going into detail about how to manage AJAX requests in WordPress or the specifics of creating RESTful interfaces with WP REST API.

The form

Just some simple HTML markup here:

<div id="result"></div>

<form id="form">
	<input type="text" id="email" />
	<input type="submit" />
</form>

The admin-ajax way

First,  we enqueue the JS file and then use wp_localize_script to provide a global object to our JavaScript file. We then use the wp_ajax_nopriv hook to handle an unauthenticated ajax request.

I have prefixed everything with ajax_ in this example. So let’s do the enqueues:

add_action( 'wp_enqueue_scripts', 'ajax_site_scripts', 999) ;
function ajax_site_scripts() {
    // Enqueue our JS file
    wp_enqueue_script( 'ajax_appjs',
      get_template_directory_uri() . '/js/app.js',
      array( 'jquery' ), filemtime( get_template_directory() . '/js/app.js'), true
    );
    
    // Provide a global object to our JS file containing the AJAX url and security nonce
    wp_localize_script( 'ajax_appjs', 'ajax_object',
        array(
            'ajax_url'      => admin_url('admin-ajax.php'),
            'ajax_nonce'    => wp_create_nonce('ajax_nonce'),
        )
    );
}

And then our AJAX handler:

add_action( 'wp_ajax_nopriv_ajax_validate_email', 'ajax_validate_email' );
function ajax_validate_email() {
    // Check Security Nonce
    if ( !wp_verify_nonce( $_POST['security'], 'ajax_nonce') ) {
        wp_send_json_error( array('message' => 'Nonce is invalid.') );
    }
    
    // Check if email is not supplied or empty
    if ( !isset($_POST['email']) || empty($_POST['email'])) {
        wp_send_json_error( array('message' => 'No email supplied.') );
    }
    
    // Here we validate our email
    if ( is_email( $_POST['email'] ) ) {
        wp_send_json_success( array('message' => 'Email is valid.') );
    }
    
    // If the previous check didn't pass, then our email is invalid
    wp_send_json_error( array('message' => 'Email is not valid.') );
}

And here is the Javascript:

jQuery(document).ready(function($) {
  $( '#form' ).on( 'submit', function(e) {
      e.preventDefault();
      
      // Get our email from the input
      var email = $( '#email' ).val();
      
      // Create our data object, contaning email, ajax action, and the nonce.
      var data = {
          email: email,
          action: 'ajax_validate_email',
          security: ajax_object.ajax_nonce
      };
      
      // Fire the AJAX request!
      $.ajax({
          method: 'POST',
          url: ajax_object.ajax_url,
          data: data,
          success : function( response ) {
            $( '#result' ).html(response.data.message);
          },
          fail : function( response ) {
            $( '#result' ).html(response.data.message);
          }
      });
  });
});

The WP REST API way

Now here is how to achieve the same result using a custom endpoint created with the WP REST API.

I have prefixed everything with rest_ in this example. Here we do the enqueues again, very similar to the admin-ajax method.

add_action( 'wp_enqueue_scripts', 'rest_site_scripts', 999 );
function rest_site_scripts() {
    // Enqueue our JS file
    wp_enqueue_script( 'rest_appjs',
      get_template_directory_uri() . '/js/app.js',
      array( 'jquery' ), filemtime( get_template_directory() . '/js/app.js'), true
    );
    
    // Provide a global object to our JS file contaning our REST API endpoint, and API nonce
    // Nonce must be 'wp_rest' !
    wp_localize_script( 'rest_appjs', 'rest_object',
        array(
            'api_nonce' => wp_create_nonce( 'wp_rest' ),
            'api_url'   => site_url('/wp-json/rest/v1/')
        )
    );
}

And then we register the rest endpoint and provide a callback to the handler using the register_rest_route function. Technically you could just provide a validation callback for the email argument, but I’m ignoring that to retain simplicity in the comparison.

add_action( 'rest_api_init', 'rest_validate_email_endpoint' );
function rest_validate_email_endpoint() {
    // Declare our namespace
    $namespace = 'rest/v1';

    // Register the route
    register_rest_route( $namespace, '/email/', array(
        'methods'   => 'POST',
        'callback'  => 'rest_validate_email_handler',
        'args'      => array(
            'email'  => array( 'required' => true ), // This is where we could do the validation callback
        )
    ) );
}

// The callback handler for the endpoint
function rest_validate_email_handler( $request ) {
    // We don't need to specifically check the nonce like with admin-ajax. It is handled by the API.
    $params = $request->get_params();
    
    // Check if email is valid
    if ( is_email( $params['email']) ) {
        return new WP_REST_Response( array('message' => 'Valid email.'), 200 );
    }
    
    // Previous check didn't pass, email is invalid.
    return new WP_REST_Response( array('message' => 'Not a valid email.'), 200 );
}

And here is the Javascript – remember to read the comments!

jQuery(document).ready(function($) {
  $( '#form' ).on( 'submit', function(e) {
      e.preventDefault();

      // Get our email from the input
      var email = $( '#email' ).val();

      // Compile our data object - notice that it is only the email as opposed to the admin-ajax method.
      // The nonce is sent in a request header - see beforeSend in $.ajax
      // No action is required, we specify the direct endpoint we created above as the url in $.ajax
      var data = {
          email: email,
      };
      
      // Fire our ajax request!
      $.ajax({
          method: 'POST',
          // Here we supply the endpoint url, as opposed to the action in the data object with the admin-ajax method
          url: rest_object.api_url + 'email/', 
          data: data,
          beforeSend: function ( xhr ) {
              // Here we set a header 'X-WP-Nonce' with the nonce as opposed to the nonce in the data object with admin-ajax
              xhr.setRequestHeader( 'X-WP-Nonce', rest_object.api_nonce );
          },
          success : function( response ) {
            console.log(response);
            $( '#result' ).html(response.message);
          },
          fail : function( response ) {
            console.log(response);
            $( '#result' ).html(response.message);
          }
      });
  });
});

Going further

I hope this comparison helped you become more familiar with the new REST API. With the release of WordPress 4.7, default endpoints (posts, pages, users..) will be merged into core. Typically if you were interacting with data associated with these endpoints you would use and extend these endpoints rather than creating your own custom endpoint. Have a look at the feature plugin documentation to find out more.

Discussion (2 comments)

  1. Hey mate,

    Thanks for this article. I was looking for a way to verify the nonce for a REST API request via wp_verify_nonce(). I didn’t know the API itself will do that for you.

    Is there a way to test the nonce verification via the API ?

    Regards Jerome

Leave a Reply

Your email address will not be published. Required fields are marked *