Hello again everybody! Welcome back to another edition of the REST Easy tutorial blog series. Each week we take some aspect of building an API in Drupal 7 with the RESTful module and walk through the process of realizing it in our own API. Over the course of the series, we’ve built a node and taxonomy endpoint, added a number of attributes to our endpoints for a variety of Drupal field types, and taken a look at filtering our endpoints using EFQ.
Two quick side-notes before we get down to business this week:
- If you are enjoying this series and will be attending Drupalcon Europe – why not attend our training? Four Kitchen’s web chefs Matt Grill and Patrick Coffey will guide you through everything you need to know about the RESTful Module and using a Node front end (Saucier) framework to consume that API. This is the real deal guys, don’t miss it: Register for headless Drupal training.
- Matt Grill and I were invited to speak with Stephen Cross and John Picozzi at Talking Drupal podcast last week about our work with the RESTful module and decoupled Drupal 7 for Twit.tv. Take a listen for some good discussion about decoupled approaches and techniques: Talking Drupal #102.
Happy together: Connecting entities to each other in our API
This week we are going to learn about hypermedia in APIs and then add our genre taxonomy field to our artist content type in Drupal. Then we’ll populate our content with a few connections so we can explore our API with appropriate data. Finally we’ll look at ways to create Hypermedia connections in our API between entities and how they look in our endpoint output.
Reference material
In Drupal we’ve had the entity reference functionality for a good long while and before that we had node references. This functionality gives us the ability to connect entities together, essentially to embed an entity within an entity. What we are really thinking about when we do this is creating a relationship, a data relationship between those entities that we can use with theming, views, and perhaps panels to enrich our content. In Drupal this typically has some kind of front end impact, for instance, we might show a link to the referenced item or display some version of the embedded entity within the parent entity.
Kentucky Fried Hypermedia: _links
and _embed
in APIs
How do we represent such a connection in an API? Well, there are a number of ways you might come up with to do that, you might add an attribute to your API or something like that. However, there is already a really great way to do this. It’s called hypermedia. That might sound familiar to you. It’s a huge part of the web and its mark up language, HTML (Hypermedia Text Markup Language). It’s been a long time since I first learned HTML, at the time — it was important to talk about what those letters meant because we had no idea, it was all new. Maybe now HTML is like KFC, which used to be “Kentucky Fried Chicken”, now it is just KFC. Its gone beyond place, beyond chicken. I guess what I wonder is whether HTML is beyond chicken now? I digress.
In HTML, hypermedia allows us to embed resources like images through <img>
tags within pages. Additionally, hypermedia allows us to navigate between pages through links. Both of these elements are hugely important to the richness of the web. Luckily we have access to very similar behaviors in our APIs with hypermedia capable formats such as hal and json api. Formats such as this expose hypermedia API elements such as _embed and _links. These elements have specific behaviors and implications that clients understand and allow. We’ll take a look at _links in a future tutorial and see the power of states and transitions. Today, we are going to focus on representing our embedded resources through _embeds.
What’s the big deal though? Can’t we just add an attribute with a url value and tell people in our documentation that we mean that to be a link? Well, no one can stop you from making that choice. In fact many, many web APIs do just that. However, it’s essentially equivalent to the following in HTML:
No really this is a link, just paste it into your web browser: https://twitter.com/beautyhammer
Versus:
<a href="https://twitter.com/beautyhammer">https://twitter.com/beautyhammer</a>
Yes, you can provide documentation hoping that your data will be contextualized correctly – OR – you can use the core behaviors of hypermedia to do what they were meant to do in the first place.
As with all of these tutorials you can follow along below and build upon the work you’ve completed in lesson 1-4 or grab the code at the corresponding tag to this lesson from github REST Easy (which will have features for the node and taxonomy and the API up until this point). This tutorial will assume you have done one or the other of these and have the necessary files for your API.
Step 1: Connect artists to genres in Drupal
Before we can represent any kind of connection in our API we need to connect our data in our content model.
While there are a number of modules that manage relationships and references, Entity Reference is the easiest to use and integrate with the RESTful module.
Download the entity reference module from entityreference or:
drush dl entityreference drush pm-enable entityreference
Step 2: Add an entity reference on the artist content type to genre taxonomy
With the entity reference module enabled we can create reference fields in our fieldable entities. Let’s add a entity reference field to our Artist
content type.
Go to the Artist
content type field management interface at:
/admin/structure/types/manage/artist/fields
Enter in the following information as a new field:
Label: Artist Genre Machine Name: field_artist_genre Field Type: Entity Reference Widget: Autocomplete
It should look something like this on the entry screen:
On the field configuration screen, all of the default settings will be just fine for our purposes:
Great, that’s a fully functioning entity reference field pointing to our Genre taxonomy.
Step 3: Edit your content types with some relevant data
For the purposes of this tutorial it would be nice to populate that field so we can see how it behaves with actual content. So let’s populate that field for our existing artist content. Add the following example values for the Artist Genre
field to the artist content from tutorial 1:
ARTIST | GENRE |
---|---|
The Beatles | Merseybeat |
Chuck Berry | Rock n’ Roll |
The Jimi Hendrix Experience | Rock |
My Bloody Valentine | Dream Pop |
Raw power
There are two ways to work with entity reference fields in your RESTful module API code: raw and refined. The raw approach will remind you of our initial pass on exposing sub-properties from tutorial 2 – a lot of API guts will show. The refined approach offers a lot more control and would be how I would reccomend you approach your entity reference fields. I want to show you both, starting with the raw approach, mostly so that you will be able to compare and contrast.
Personally, I find the raw approach to expose too many implementation details which, as we discussed in our previous tutorials, is not great RESTful API practice. Any execution that exposes implementation details becomes tightly coupled to its backend implementation – and while it may be immediately useful to you to use some of the guts of an implementation – in the long run it’s going to make it harder to be portable and flexible.
Step 4: Add files and folders to your Artist endpoint for the new minor version
Please note: Because this is a tutorial series, endpoint versions are not uniform as you’d expect them to be with an official version release.
We can add raw entity output by creating a public field for our genre entity reference field on our Artist content type. To start we should make a new minor version of our Artist endpoint by adding the following folders and files:
Create the following folder and files inside of:
plugins/restful/node/artist/
plugins/restful/node/artists/0.3/artists__0_3.inc
- This will hold our plug-in definitions
plugins/restful/node/artists/0.3/ResteasyRestfulEntityArtistsResource__0_3.class.php
- This is our new 0.3 class file for all of our new additions this week.
Your folder structure should look something like this:
Step 5 – Add a plugin definition for our Artist endpoint.
As with our first tutorial we need to add in a ctools plugin definition for our Artist endpoint. For reference you can look at the first tutorial if you need a refresher on what any of the following array attributes mean. Basically we are telling the RESTful module (via ctools) all that it needs to know to find and use the class files it needs to produce a new version of the “artists” endpoint:
Add the code below to the plugin file at:
plugins/restful/node/artists/0.3/artists__0_3.inc
/** * @file * Artists plugin definition. */ $plugin = array( 'label' => t('Artists'), 'resource' => 'artists', 'name' => 'artists__0_3', 'entity_type' => 'node', 'bundle' => 'artist', 'description' => t('This resource presents artists from REST easy.'), 'class' => 'ResteasyRestfulEntityArtistsResource__0_3', 'major_version' => 0, 'minor_version' => 3, );
We will also need to add in that new public field definition for our genre entity reference field.
/** * Example only -- don't implement this version unless you need to. */ $public_fields['artistGenre'] = array( 'property' => 'field_artist_genre', );
Here is the full class file at:
plugins/restful/node/artists/0.3/ResteasyRestfulEntityArtistsResource__0_3.class.php
/** * @file * Contains ResteasyRestfulEntityArtistsResource__0_3. */ /** * Implements RestfulEntityBaseNode class for the "artist" content type. */ class ResteasyRestfulEntityArtistsResource__0_3 extends RestfulEntityBaseNode { /** * Overrides RestfulEntityBaseNode::publicFieldsInfo(). */ public function publicFieldsInfo() { $public_fields = parent::publicFieldsInfo(); $public_fields['yearFormed'] = array( 'property' => 'field_artist_year_formed', ); $public_fields['countryOfOrigin'] = array( 'property' => 'field_artist_country_of_origin', ); $public_fields['rockAndRollHallOfFameInductee'] = array( 'property' => 'field_artist_rnr_hall_of_fame', ); $public_fields['description'] = array( 'property' => 'body', 'sub_property' => 'value', ); $public_fields['artistWebsite'] = array( 'property' => 'field_artist_website', 'sub_property' => 'url', ); $public_fields['artistGenre'] = array( 'property' => 'field_artist_genre', ); return $public_fields; } }
After clearing the cache and ensuring that your endpoint verison is available, visit your endpoint at /api/v0.3/artists
you’ll see something like this:
{ "data":[ { "id":1, "label":"The Beatles", "self":"http://resteasy.local.dev/api/v0.3/artists/1", "yearFormed":"1960", "countryOfOrigin":"England", "rockAndRollHallOfFameInductee":true, "description":null, "artistWebsite":null, "artistGenre":{ "tid":"6", "vid":"2", "name":"Merseybeat", "description":"After bands from Liverpool and nearby areas beside the River Mersey is a pop and rock music genre that developed in the United Kingdom in the early 1960s.", "format":"filtered_html", "weight":"0", "vocabulary_machine_name":"taxonomy_genre" } }, { "id":2, "label":"Chuck Berry", "self":"http://resteasy.local.dev/api/v0.3/artists/2", "yearFormed":"1955", "countryOfOrigin":"USA", "rockAndRollHallOfFameInductee":true, "description":null, "artistWebsite":null, "artistGenre":{ "tid":"1", "vid":"2", "name":"Rock n\u0027 Roll", "description":"A type of popular dance music originating in the 1950s, characterized by a heavy beat and simple melodies.", "format":"filtered_html", "weight":"0", "vocabulary_machine_name":"taxonomy_genre" } }, { "id":3, "label":"The Jimi Hendrix Experience", "self":"http://resteasy.local.dev/api/v0.3/artists/3", "yearFormed":"1966", "countryOfOrigin":"USA", "rockAndRollHallOfFameInductee":true, "description":null, "artistWebsite":"http://jimihendrix.com", "artistGenre":{ "tid":"3", "vid":"2", "name":"Rock", "description":"Harsher and often self-consciously more serious than its predecessors, it was initially characterized by musical experimentation and drug-related or anti-Establishment lyrics.", "format":"filtered_html", "weight":"0", "vocabulary_machine_name":"taxonomy_genre" } }, { "id":4, "label":"My Bloody Valentine", "self":"http://resteasy.local.dev/api/v0.3/artists/4", "yearFormed":"1983", "countryOfOrigin":"Ireland", "rockAndRollHallOfFameInductee":false, "description":"u003Cpu003EMy Bloody Valentine are an alternative rock band formed in Dublin, Ireland in 1983. Since 1987, the band\u0027s lineup has consisted of founding members Kevin Shields (vocals, guitar) and Colm u00d3 Cu00edosu00f3ig (drums), with Bilinda Butcher (vocals, guitar) and Debbie Googe (bass). The group is known for their integration of distorted noise and ethereal melody, and for their innovative utilization of unorthodox guitar and production techniques. Their work in the late 1980s and early 1990s resulted in their pioneering a musical style known as shoegazing.u003C/pu003Enu003Cpu003E(from u003Ca href=\u0022https://en.wikipedia.org/wiki/My_Bloody_Valentine_%28band%29\u0022u003Ehttps://en.wikipedia.org/wiki/My_Bloody_Valentine_%28band%29u003C/au003E)u003C/pu003En", "artistWebsite":null, "artistGenre":{ "tid":"2", "vid":"2", "name":"Dream Pop", "description":"A genre of music where timbre and texture are as important, if not more so, than melody and song structure.", "format":"filtered_html", "weight":"0", "vocabulary_machine_name":"taxonomy_genre" } } ], "count":4, "self":{ "title":"Self", "href":"http://resteasy.local.dev/api/v0.3/artists" } }
As you can see, we have a good amount of details that were surfaced to us for our new field:
{ "artistGenre":{ "tid":"6", "vid":"2", "name":"Merseybeat", "description":"After bands from Liverpool and nearby areas beside the River Mersey is a pop and rock music genre that developed in the United Kingdom in the early 1960s.", "format":"filtered_html", "weight":"0", "vocabulary_machine_name":"taxonomy_genre" }
However, these details are actually quite different from the details we surfaced in our taxonomy endpoint in our fourth tutorial. We spent good effort choosing fields and surfacing an endpoint for our genres taxonomy. In terms of embedding another piece of data within our endpoint, it’s more about embedding its representation, as surfaced in the API, then all of these details. Therefore, for that and the other reasons around not exposing implementation we are going to pursue a second approach to embedding this content within our artists endpoint.
Step 6: Revise our genre public field for refined presentation
The approach is actually pretty similar, but like sub-property fields (tutorial 2), we need to add a few more attributes to our public field definition to get the improved embedding behavior. The new attributes are as follows:
$public_fields['artistGenre'] = array( 'property' => 'field_artist_genre', 'resource' => array( 'taxonomy_genre' => 'genres', ), );
Just as before we have a property mapped to the field name 'property' => 'field_artist_genre',
and in addition we now include information about the resource we created to represent that entity.
'resource' => array( 'taxonomy_genre' => 'genres', ),
Which takes the format of:
$public_fields['{Your custom field name}'] = array( 'property' => '{entity reference field machine name}', 'resource' => array( '{machine name of the entity you are referencing}' => '{endpoint name of the entity you are referencing}', ), );
Essentially we are telling our endpoint to embed an entirely separate endpoint, genres
within our artists
endpoint, that we want the same public fields and other implementation choices in the genres endpoint to be available in its embedded public field representation. Do note that we needed to create an endpoint for our referenced entity, which is what we did last week, and that this tutorial is about embedding resources. If you simply want to use entity reference fields without representing it as an endpoint, you do have options (callbacks) to help you and we’ll expose those in other tutorials.
So let’s change our artistGenre
public field definition in our artists__0_3 class to match this new implementation. Modify the full class file at
plugins/restful/node/artists/0.3/ResteasyRestfulEntityArtistsResource__0_3.class.php
/** * @file * Contains ResteasyRestfulEntityArtistsResource__0_3. */ /** * Implements RestfulEntityBaseNode class for the "artist" content type. */ class ResteasyRestfulEntityArtistsResource__0_3 extends RestfulEntityBaseNode { /** * Overrides RestfulEntityBaseNode::publicFieldsInfo(). */ public function publicFieldsInfo() { $public_fields = parent::publicFieldsInfo(); $public_fields['yearFormed'] = array( 'property' => 'field_artist_year_formed', ); $public_fields['countryOfOrigin'] = array( 'property' => 'field_artist_country_of_origin', ); $public_fields['rockAndRollHallOfFameInductee'] = array( 'property' => 'field_artist_rnr_hall_of_fame', ); $public_fields['description'] = array( 'property' => 'body', 'sub_property' => 'value', ); $public_fields['artistWebsite'] = array( 'property' => 'field_artist_website', 'sub_property' => 'url', ); $public_fields['artistGenre'] = array( 'property' => 'field_artist_genre', 'resource' => array( 'taxonomy_genre' => 'genres', ), ); return $public_fields; } }
After clearing the cache and ensuring that your endpoint verison is available, visit the improved endpoint at /api/v0.3/artists
you’ll see something like this:
{ "data":[ { "id":1, "label":"The Beatles", "self":"http://resteasy.local.dev/api/v0.3/artists/1", "yearFormed":"1960", "countryOfOrigin":"England", "rockAndRollHallOfFameInductee":true, "description":null, "artistWebsite":null, "artistGenre":{ "id":"6", "label":"Merseybeat", "self":"http://resteasy.local.dev/api/v0.1/genres/6", "genreDescription":"u003Cpu003EAfter bands from Liverpool and nearby areas beside the River Mersey is a pop and rock music genre that developed in the United Kingdom in the early 1960s.u003C/pu003En", "weight":"0" } }, { "id":2, "label":"Chuck Berry", "self":"http://resteasy.local.dev/api/v0.3/artists/2", "yearFormed":"1955", "countryOfOrigin":"USA", "rockAndRollHallOfFameInductee":true, "description":null, "artistWebsite":null, "artistGenre":{ "id":"1", "label":"Rock n\u0027 Roll", "self":"http://resteasy.local.dev/api/v0.1/genres/1", "genreDescription":"u003Cpu003EA type of popular dance music originating in the 1950s, characterized by a heavy beat and simple melodies.u003C/pu003En", "weight":"0" } }, { "id":3, "label":"The Jimi Hendrix Experience", "self":"http://resteasy.local.dev/api/v0.3/artists/3", "yearFormed":"1966", "countryOfOrigin":"USA", "rockAndRollHallOfFameInductee":true, "description":null, "artistWebsite":"http://jimihendrix.com", "artistGenre":{ "id":"3", "label":"Rock", "self":"http://resteasy.local.dev/api/v0.1/genres/3", "genreDescription":"u003Cpu003EHarsher and often self-consciously more serious than its predecessors, it was initially characterized by musical experimentation and drug-related or anti-Establishment lyrics.u003C/pu003En", "weight":"0" } }, { "id":4, "label":"My Bloody Valentine", "self":"http://resteasy.local.dev/api/v0.3/artists/4", "yearFormed":"1983", "countryOfOrigin":"Ireland", "rockAndRollHallOfFameInductee":false, "description":"u003Cpu003EMy Bloody Valentine are an alternative rock band formed in Dublin, Ireland in 1983. Since 1987, the band\u0027s lineup has consisted of founding members Kevin Shields (vocals, guitar) and Colm u00d3 Cu00edosu00f3ig (drums), with Bilinda Butcher (vocals, guitar) and Debbie Googe (bass). The group is known for their integration of distorted noise and ethereal melody, and for their innovative utilization of unorthodox guitar and production techniques. Their work in the late 1980s and early 1990s resulted in their pioneering a musical style known as shoegazing.u003C/pu003Enu003Cpu003E(from u003Ca href=\u0022https://en.wikipedia.org/wiki/My_Bloody_Valentine_%28band%29\u0022u003Ehttps://en.wikipedia.org/wiki/My_Bloody_Valentine_%28band%29u003C/au003E)u003C/pu003En", "artistWebsite":null, "artistGenre":{ "id":"2", "label":"Dream Pop", "self":"http://resteasy.local.dev/api/v0.1/genres/2", "genreDescription":"u003Cpu003EA genre of music where timbre and texture are as important, if not more so, than melody and song structure.u003C/pu003En", "weight":"0" } } ], "count":4, "self":{ "title":"Self", "href":"http://resteasy.local.dev/api/v0.3/artists" } }
Well, now our API is really starting to take shape. The representation of genre found in our endpoint is exactly as it is in the genres endpoint. We’ve added an embedded entity into our endpoint. In terms of representing the data relationship in hypermedia, well, we are almost there. We need the power of a hypermedia capable format for our API. That is exactly what we will do next week when we’ll take a look at hal+json and format options for the RESTful module and our API.
Read more of the REST easy series:
- REST easy, part 1: And the RESTful is up to you!
- REST easy, part 2: Sub property boogaloo
- REST easy, part 3: Now filter this
- REST easy, part 4: The tax(onomy) man!
- REST easy, part 5: Everybody get together
Making the web a better place to teach, learn, and advocate starts here...
When you subscribe to our newsletter!