How to Build a RESTful API for a Front-end Framework with Drupal 7

Building Drupal Rest API

JavaScript frameworks gained big popularity during the last few years, and some people truly believe that server-generated front-end is no longer an option. Even if that might not be true, we have to admit that in most cases front-end frameworks provide much better user experience, reduce the server-side load (we can simply move a part of logic to the client) and generally make surfing feel much smoother and faster. Luckily, owners of Drupal-based websites can take advantage of this, as there are some exciting tools that may turn Drupal into a reliable RESTful back-end.

Personally, I've been working with Angular JS for a year, and despite all the criticism I fully understand how awesome it is. You can add a client-side form validation just with a couple of lines in your template and display the errors to a user instantly, without having them to submit the form and wait for the page to load. No more messing with clumsy renderable arrays and HTML-preprocess hooks – the templates are just HTML-files and you can give them whatever structure you need to by simply editing them. Along with ever growing number of JavaScript libraries wrapped into Angular services and directives, it felt and still feels like magic, and I thought that at some point it would be great to bring this portion of magic to my humble blog. Now the time has almost come.

Front-end frameworks communicate with back-end (in our case, Drupal) via asynchronous XHR requests. To make this work, we need to configure endpoints on the Drupal side that would return the necessary data in JSON format.

Drupal REST building tools

Before we start exploring the REST-building tools Drupal.org has prepared, let's list the things we need to be available to access via API:

  1. Blog Article. Even though it might seem obvious to have JSON representation of the node entity, it might also be good to get some fields from the related entities, such as node's author, comments, etc.
  2. Article list. Most certainly, it's a Drupal view. We need to find a way to turn views into JSON.
  3. Blocks. Blocks can and should be built with Views, so unless your blog has a sort of special non-views block, the Views-to-REST solution should cover this item.
  4. Drupal Forms and Webforms. We need to be able to submit webforms (made with the Webform module) via a REST call. Also, in the future, it would be great to register users, post comments, and make any other interactions with Drupal from the separated front-end.

Fortunately, there is an old and tested solution called Services. Out of the box, the Services module provides possibilities to perform Create / Update / Retrieve / Delete operations on standard Drupal entities via REST calls. If you have never used Services before, you might want to take a look at its official documentation which contains detailed guides and screen-casts on getting started.

There is also a Services-based module to transform Drupal Views into REST endpoints, which is known as Services Views. To get a quick overview, please watch this author's video tutorial. Pay attention, that it adds a new Services display type to Views, and in the rest of the article we'll be working with Views displays of this type only.

And last but not least, Services Webforms is going to help us to submit a contact Webform via an XHR request.

Even though the implementation with already built modules might seem fast and simple, you always discover troubles you could not predict beforehand. Here I am also going to describe some issues I stumbled upon and the solutions to them.

Let's get started!

Creating an Endpoint for a Blog Article

The first idea you may come up with is to simply use the node resource, and it would work if you do not care about nice URLs. Considering the migration case, you would want to keep the URL aliases you had before, so that any old links to your website don't break at once. So basically you need to pass the URL from the address bar to back-end and get the node corresponding to this URL in response. That's what Views exposed filter would typically do, so it might be a better to use a View instead. Using a View would give us a flexibility to get fields of other related entities such as node's author, node's comments or anything else. However the views approach has two little drawbacks:

  1. You cannot display node's metatag data in a View (it is included in the output when using the node resource).
  2. URL aliases are not listed in the available node fields, either.

Fortunately, Drupal has a rich community, which already faced these issues and wrote the solutions for us.

Displaying Metatags as a Views Field

There is an issue on Drupal.org dedicated to this point. The first attached module, metatag_views_field.zip, seems to be working, but when you try to configure a metatag data field, you will find that if you can only use the “Meta description". Why all the other fields are missing? Let's find out by inspecting the module's code.

// metatag_views_field.views.inc
 
function metatag_views_field_views_data() { 
...
 // Define the field.
 $data['metatag']['data'] = array(
 'title' => t('Data'),
 'help' => t('A full serialized array.'),
 'field' => array(
 'field' => 'data', // The real field.
 'group' => t('Metatag'), // The group it appears in on the UI.
 'handler' => 'views_handler_field_node_metatag',
 'click sortable' => TRUE,
 ),
...
 return $data;
}

As you can see from hook_views_data, this field displays the contents of the data column in the metatag table. This is where the Metatag module keeps all its data as a serialized array, like this:

a:2:{s:11:"description";a:1:{s:5:"value";s:165:"A brief introduction how to set up a virtual linux server for Drupal development using vagrant and how to make work with a Vagrant server easy with the PhpStrom IDE.";}s:8:"keywords";a:1:{s:5:"value";s:113:"vagrant virtual machine drupal, vagrant phpstorm, puppet virtual machine drupal, virtual machine setup for Drupal";}}

If we have all the data at the same place, why can't we just add another fields to the configure form? That's a good question. Here's the code that stands for the form:

// views_handler_field_node_metatag.inc
 function options_form(&$form, &$form_state) {
 parent::options_form($form, $form_state);
 
 $options = array();
 $options['description'] = t('Meta description');
 
 
 $form['data_key'] = array(
 '#title' => t('Data key'),
 ...
 );
 }

Okay, so that's it. We've got only the description option. What if we add another options, like 'keywords', and try to use it, would it display them correctly?

 $options = array();
 $options['description'] = t('Meta description');
 $options['keywords'] = t('Keywords');

It would. But there's one little flaw in the render function. Unlike JavaScript, PHP would display a notice when you try to access a property of an array or object which is not defined. So we had better add a little check and fallback to an empty string in case a node has no description or no keywords:

 function render($values) {
 $value = $values->{$this->field_alias};
 $data = unserialize($value);
 $rendered_value = isset($data[$this->options['data_key']]['value']) ? $data[$this->options['data_key']]['value'] : '';
 return $rendered_value;
 }

That's it. If we add metatags fields and take a look a View preview, we'll find the following lines among the output JSON:

 "metatag_description" => "How to switch different versions...",
"metatag_keywords" => "node js versons, node js nvm, ...",

Done deal. Let's move forward.

Displaying Node Alias as a View Field

Another task is to get the node URL alias display as a view field so that we could use it as a filter and get the right node by providing its nice URL in request. Fortunately, there's a full-project module for that, Views URL Alias, and this time we're not going to mess up with code. It adds a new field, Node: URL alias, to views, and it works exactly how we need. Use it as an exposed filter, and a GET request to http://example.com/rest/articles?alias=

Creating an Endpoint for Article List

For the article list, we have to create another View with fields we need to display, including aforementioned URL alias field, which would be used as link's href in our HTML template. Thanking to views flexibility, we can use one single view for several category pages by filtering the output by a certain taxonomy ID. Even though it is explained in the Services Views video tutorial, I found it a little bit challenging to construct a right request URL. The easiest way is to create a copy of a View with its normal (page) display, set up exposed filters you need, play with the filtering form and write the right URLs from the browser's address bar.

Paging

We can also control the number of items to display and the page number by supplying correct arguments in URL. To be able to control the number of items, you need to check "Expose items per page" in the pager options. Having it checked, you get the desired output with URL like the following:

http://example.com/rest/articles?page=2&items_per_page=5 
// Displays the second page with 5 articles

The issue with front-end navigation is that we won't know what page we are currently on and how many pages the view has at all – the response does not contain such data. Once again, I managed to find a solution among the countless number of Drupal sandboxes - Services Views Totals. It adds to the output the following data structure:

  "metadata": {
    "current_page": 0,
    "total_pages": 1,
    "items_per_page": "10",
    "display_start": 1,
    "display_end": 10,
    "total_results": "11"
  }

That's more than enough! Thanks Christopher M. Church for your work.

Submitting a Webform

Is there already a module that would allow to submit a Webform via a REST call? Of course, there is. Its name is Webform Service. For this example, we'll use the 7.x-4.0-beta1 version which makes use of Universal Unique Identifiers (UUID) and seems reliable.

The steps are relatively simple:

  1. Enable Webform Service and the “Submission > Create" resource in your endpoint's resources
  2. Submit the following data structure to http://example.com/rest/submission
{
 "webform":"<YOUR WEBFORM UUID>",
 "submission":{
 "data":{
 "1":{ "values": ["Ramin Popygaev"] },
 "2":{ "values": ["email@web-mystery.com"] },
 "3":{ "values": ["Congratulations! You won a million dollars, please send our bank all your personal data so that we could transfer the money to your bank account!"] }
 }
 }
}

The keys in the data object correspond to the component IDs in your webform.

But how do I know the UUID assigned to a certain webform? That's a good question. One of the ways is to ask for full index of webforms on http://example.com/rest/webform – the response will contain the full list of webforms on your website with their UUIDs. Just don't forget to enable “Webform > Index" resource first and disable it once you've finished.

If everything went good, you will received the full submission object in response.

How about registering users or posting comments? This can be achieved a little bit easier with Services out of the box. You need to enable Create operations on Users or Comments via REST API and it would work similarly with a POST request.

Afterword

So here it is. Hope, now you have a good idea on how to build a REST API based on Drupal 7 and will save some time on issues I faced. Thanks for reading and have fun with Drupal!