3 Helpful Hints for using Drupal 8 REST APIs

Posted By Brad Czerniak on Thursday, February 18, 2016 - 13:48

A recent internal Commercial Progression project allowed us the opportunity to set up a Drupal 8 site and get REST working on it. In the process, and by merit of the stack being new with the documentation catching up, I learned a few tricks that might be useful for enterprising developers out there.

1. POST-ing entities is to a different path than the REST UI says

If you install the REST UI module and enable an endpoint, you'll see a screen like:

Two REST endpoints with paths and settings

From this display, you might be convinced that the URL you use to HTTP POST a new entity (if the bundle were named "update", for instance) would be

http://yourname.tld/admin/structure/eck/entity/component/update

But you would be wrong. The actual post path is:

http://yourname.tld/entity/component

...where "component" is the entity type. This seems to be the case for pretty much any entity. Don't forget the

?_format=hal_json

on all your stuff, for good measure.

2. There's a thing called a CSRF token, and you can get one with cURL

The documentation lets you know that risky operations require a CSRF token and how to get one. Yuen Ying Kit even has the necessary Guzzle-based PHP code (posted in 2013!) for grabbing a CSRF token on the fly.

Let's say you wanted to write it for a cURL request in PHP without any external dependencies. That would look something like:

  1. // Log in.
  2. $fields = array(
  3.   'name' => 'username',
  4.   'pass' => 'this_is_where_the_password_goes',
  5.   'form_id' => 'user_login_form',
  6. );
  7. // Cheesy way to urlencode a POST string
  8. $fields_string = '';
  9. foreach ($fields as $key => $value) {
  10.   $fields_string .= $key . '=' . urlencode($value) . '&';
  11. }
  12. rtrim($fields_string, '&');
  13.  
  14. $process = curl_init('http://yourname.tld/user');
  15. curl_setopt($process, CURLOPT_TIMEOUT, 30);
  16. curl_setopt($process, CURLOPT_RETURNTRANSFER, TRUE);
  17. curl_setopt($process, CURLOPT_POST, count($fields));
  18. curl_setopt($process, CURLOPT_POSTFIELDS, $fields_string);
  19. $login = curl_exec($process);
  20.  
  21. // Fetch a new CSRF token.
  22. curl_setopt($process, CURLOPT_URL, 'http://yourname.tld/rest/session/token');
  23. $csrf_token = curl_exec($process);
  24. curl_close($process);

The $csrf_token variable now contains a working token for POST-ing and doing other risky operations.

For a cURL request to do such an operation, your HTTP header should include the following:

  1. curl_setopt($process, CURLOPT_HTTPHEADER, array(
  2.   'Content-Type: application/hal+json',
  3.   'Accept: application/json',
  4.   'X-CSRF-Token: ' . $csrf_token,
  5. ));
  6. curl_setopt($process, CURLOPT_USERPWD, $fields['name'] . ':' . $fields['pass']);

As long as you have an acceptable payload in your CURLOPT_POSTFIELDS, you should be off to the races!

3. Entity reference fields work how you would expect, except not

If you GET an entity with an entityreference (Reference? Entity reference?) field as HAL JSON, the result will include a number of interesting tidbits that indicate the reference:

  1. {
  2.   "_links": {
  3.     "http://yourname.tld/rest/relation/component/component_update/field_update_site": [
  4.       {
  5.         "href": "http://yourname.tld/node/3?_format=hal_json"
  6.       }
  7.     ]
  8.   }, 
  9.   "_embedded": {
  10.     "http://yourname.tld/rest/relation/component/component_update/field_update_site": [
  11.       {
  12.         "_links": {
  13.           "self": {
  14.             "href": "http://yourname.tld/node/3?_format=hal_json"
  15.           }, 
  16.           "type": {
  17.             "href": "http://yourname.tld/rest/type/node/site"
  18.           }
  19.         }, 
  20.         "uuid": [
  21.           {
  22.             "value": "0a2cbd4a-1bc2-40f0-a502-2e3bb0917b2b"
  23.           }
  24.         ]
  25.       }
  26.     ]
  27.   }
  28. }

These are all useful payload for interacting with the referenced entities when ingesting, but...

If you format a HAL JSON entity for POST with this structure, the reference field will not populate (at least it didn't for me). The trick is also include a direct value to the desired field, like:

  1. // This is the addition to the JSON that <em>actually</em> populates the entity.
  2. $entity['field_update_site'] = [[
  3.   'target_id' => $nid
  4. ]];

This would add JSON that looks like:

  1. {
  2.   "field_update_site": [
  3.     {
  4.       "target_id": "3"
  5.     }
  6.   ], 
  7. }

...which the backend will process into the reference. I keep the other HAL stuff along for good measure.

In closing

So that's the stuff I found in my first go at RESTful work in Drupal 8. There's bound to be way more where that came from, and we'll be sure to keep you POSTed with all the gotchas and fun tweaks we find along the way.

Looking to make a Drupal 8 site with lots of web services? Get in touch with the Drupal experts!

Drupal 8 web design and development