Over the past few years I’ve had the pleasure of working on some pretty neat decoupled web architectures - notably The Tonight Show with Jimmy Fallonand more recently the launch of the headless Drupal 7 API for TWiT.tvpowered by the RESTful module.
I’ve had the opportunity to share some high level thoughts on decoupled architectures at DrupalCon Austin, (See my talk with Andrew Berry of Lullabot on our emmy award winning collaboration with NBC) and my take on API design best practices at DrupalCon LA (API Design: The musical). It was an honor to have those opportunities and I wanted to follow up on both of those talks with some tutorials so that you too could use Drupal to quickly and powerfully serve an API of your own.
An API endpoint Tutorial with Drupal and the RESTful module.
So that brings us to this series: “REST Easy.” In this series I will take you through developing a REST API with Drupal 7 and the RESTful module. We’ll make sure you have a solid foundation and build up the API one step at a time. In this first session I’d like to cover creating an API endpoint for a simple content type in Drupal.
But first, what is the RESTful module?
RESTful is a wonderful little module that provides a solid object oriented base for implementing a REST API in Drupal. I have used both RESTful and services to create APIs and at the moment I prefer to work in RESTful - though the class based PHPoop approach is really vital to both. The maintainers are conscientious, in-tune with API best practices and write quality code. I encourage you to contribute to their project by reporting issues, contributing code in pull requests in GitHub, and most importantly using this great module.
A few assumptions:
-
You have a working Drupal website. If not, go get yourself one.
-
You are able to create and deploy custom code to that website.
-
You have a basic understanding of APIs.
We have a multi-step process that we are going to follow that looks like this:
-
Get Drupal set-up and the RESTful module installed
-
Choose or create a content type to use with your API (or use your own)
-
Add content and ensure your permissions are correct
-
Create a custom RESTful API module with a plugin exposing the content type from step 2 as an endpoint.
-
Enable your module and visit your new API!
In our first step we need to download and install the RESTful module from Drupal.org or directly from their github project:
Step 1: Getting Drupal set up with the RESTful module
Download the RESTful module into your Drupal installation (/sites/all/modules/contrib) or install via drush:
drush dl restful ctools entity
Now enable the RESTful module via the Drupal admin UI, or use drush:
drush en restful
Drush will prompt you with the following question:
The following extensions will be enabled: restful, entity, ctools
Do you really want to continue? (y/n):
Answer “yes”!
Now that we have enabled the RESTful module let’s configure it for use, starting with permissions: (/admin/people/permissions#module-restful). Please note: Just ensure that only your admin roles can administer the RESTful module.
Now let’s take a look at the RESTful configuration page. We aren’t going to touch anything today (don’t worry we’ll take a look in the weeks to come) - just showing you where it is and the options you have available (/admin/config/services/restful).
Take a look around, open up the advanced options tab, but like I said, let’s just leave this as it is for now.
Now let’s create a simple content type to use with the API. For the purposes of this series I am going to construct content types, taxonomies and other entities around a fictitious music site. However, your API could serve your existing blog posts, or classes, books, recipes or really whatever you like.
Note, you can:
-
Create these content types by hand OR
-
Download a module containing a feature with this content type from my github repo Rest Easy for this series OR
-
Use your own content type (ignore step 2 - but obviously you’ll have to have your wits about you and rename things as we go.)
Step 2: Create a simple content type
Start by navigating to admin/structure/types/add within your Drupal admin UI. Let’s create a content type called: Artist
-
Turn off commenting
-
Rename the title field to: “name”
-
Save and add the following fields:
-
Year formed
-
machine name: field_artist_year_formed
-
type - integer
-
1 value
-
Country of Origin
-
machine name: field_artist_country_of_origin
-
type: text
-
plain text
-
1 value
-
Rock and Roll Hall of Fame
-
machine name: field_artist_rnr_hall_of_fame
-
type: boolean
-
1 value
At this point you should have the following fields (the body field came a long for free):
We now need to make sure that we have good permissions and that we have some content to serve from the API.
Step 3 - Ensure permissions and add content
Now is a good point to do the following:
-
Set the edit permissions for your new content type at/admin/people/permissions.
-
Check that anonymous site visitors can see published content.
If you are following along with the Artist content type here is some data to help you add four artists to our site at /node/add/artist:
Name |
Body |
Year Formed |
Country of Origin |
Rock and Roll Hall of Fame |
---|
The Beatles |
{left blank} |
1960 |
England |
Yes |
Chuck Berry |
{left blank} |
1955 |
USA |
Yes |
The Jimi Hendrix Experience |
{left blank} |
1966 |
USA |
Yes |
My Bloody Valentine |
{left blank} |
1983 |
Ireland |
No |
OK, all of that hopefully looked pretty much like all the Drupal you’ve done before. Now it’s time to get RESTful. In this next step we are going to create a custom module for our API.
Step 4 Creating a Custom RESTful module.
(edit: Please note that because this is a tutorial series, endpoint versions are not uniform as you’d expect them to be with an official version release read the following comments for more info: http://fourword.fourkitchens.com/article/rest-easy-part-4-taxonomy-man#comment-2230720908)
-
Create a folder inside of /sites/all/modules/custom calledmyresteasy_api
-
Create several files inside of myresteasy_api. These first two belong in any custom module.
-
myresteasy_api.info
-
myresteasy_api.module
-
Now lets create some restful/ctools folder structures. Create the following folders:
-
plugins - used to organize ctools plugins
-
plugins/restful - used to organize plugins as belonging to the restful module
-
plugins/restful/node - further organization to group entities by entity type - in this case nodes.
-
plugins/restful/node/artists - the name of our first restful plugin. Notice I went with the plural form of the word artist, for endpoints, that is a best practice.
-
Now we will create a folder and two files inside of the artists folder which will help us define our first RESTful plugin.
-
plugins/restful/node/artists/0.1/
-
plugins/restful/node/artists/0.1/artists__0_1.inc - This will hold our plug in definitions
-
plugins/restful/node/artists/0.1/ResteasyRestfulEntityArtistsResource.class.php- This will hold an implementation of the Restful Entity Resource class specific to our endpoint. Sure it’s a bit long winded, but the verbosity might pay off in the the debugging stack.
It should look something like this when you are done:
Now let’s add the code we need to serve out our endpoint. Let’s start with the .info file, a good place to start for any Drupal module:
name = My REST Easy API
description = The REST Easy API
core = 7.x
dependencies[] = restful
Now let’s add in the necessary code for the .module file. This file won’t have much in it (YET) - but we do need to let ctools know where to find our plugins.
<?php
/**
* @file
* Module file for Rest EASY API.
*/
/**
* Implements hook_ctools_plugin_directory().
*/
function myresteasy_api_ctools_plugin_directory($module, $plugin) {
if ($module == 'restful') {
return 'plugins/' . $plugin;
}
}
Easy enough. How about we get on to the real heart of it then. The plugin itself. We have two files here, our plugin definition: artists__0_1.inc and our class file: ResteasyRestfulEntityArtistsResource.class.php.
Let’s start with our plugin definition file. This is where we tell Restful through a ctools plugin definition how we want our RESTful endpoint to behave and what properties it will possess. You can find a full rundown of all of the array elements in the plugin definition in the comments of the restful module at: includes/RestfulManager.php. Be assured, however, that we will be digging back into plugin definitions over the course of our tutorials.
For now we only need concern ourselves with the following:
<?php
/**
* @file
* Artists plugin definition.
*/
$plugin = array(
'label' => t('Artists'),
'resource' => 'artists',
'name' => 'artists__0_1',
'entity_type' => 'node',
'bundle' => 'artist',
'description' => t('This resource presents artists from REST easy.'),
'class' => 'ResteasyRestfulEntityArtistsResource',
'major_version' => 0,
'minor_version' => 1,
);
In our plugin we have these attributes:
-
label - This is a plain text common language label for the endpoint.
-
resource - this is the name of our endpoint, we’re using artists for this example. It is best practice for endpoints to be nouns and for them to be plural. Purists will argue that these can actually be named anything at all if your API is truly RESTful with good HATEOAS. The resource name relates to your endpoint URL directly - thus our endpoint will be at: /api/v0.1/artists
-
name - this is the name of your plugin definition.
-
entity_type - For us, right now, we are making a node endpoint, and thus - node.
-
bundle - the machine name of our our content type.
-
description - A textual description of our API endpoint that will be visible to API consumers that visit the API discovery resource (per best practice)
-
class - The name of our implementing class. This should correspond to the name of our class file (everything pre .class.php and thus - ResteasyRestfulEntityArtistsResource.class.php becomes ResteasyRestfulEntityArtistsResource
-
major_version - This field and the minor_version work together to help version your API. This attribute determines the first number (x) in this pattern {x}.{y} i.e. the number 1 in 1.0.
-
minor_version - This field and the major_version work together to help version your API. This attribute determines the second number (y) in this pattern {x}.{y} i.e. the number 2 in 1.2.
A note on API versioning: This is quite important, in fact, it works in step with your over all plan for API versioning. Your approach to this process (something for a future post) is your contract with your user base, be careful here and try to get it right the very first time. From the perspective of your API consumers, they are going to build software to consume your API at a specfic version and are going to depend on attributes and features. As an API implementer that means you need to work out your endpoints ahead of time and not alter them adhoc. Now, you could have some play, depending on your community and your hateoas approach - perhaps you can offer additional relationships or attributes but no changes to existing attributes within versions or perhaps that would be intolerable, regardless, definitely avoid removing established attributes once a version has been released.
Almost there everybody, let’s take a look at the class file. This can be quite a rich space for local implementation differences, but for now we really only need to define one function in our class, the publicFieldsInfo() function.
<?php
/**
* @file
* Contains ResteasyRestfulEntityArtistsResource__0_1.
*/
/**
* Implements RestfulEntityBaseNode class for the "artist" content type.
*/
class ResteasyRestfulEntityArtistsResource 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',
);
return $public_fields;
}
}
The publicFieldsInfo property is where we define the attributes we would like to in our custom endpoint. Here we can do things like name the attributes of our endpoint with custom names. Why not just expose the field names and be done with it? Well, that would exposing an implementation detail and which is not good API design. That would tend to couple our API with this specific implementation. Things may change in the backend out of necessity but developers who are using your API don’t care about that; they care about the applications they built and that those applications continue to work. If you choose and keep the same semantic well-named attributes you can change implementation details all you want and those apps will keep on running.
We add the fields to the public fields array by defining a new array item with our desired name with a ‘property’ attribute set to the machine name of the field. Since my intent is to output as JSON I have chosen camelCase:
$public_fields['yearFormed'] = array(
'property' => 'field_artist_year_formed',
);
With all that in place your API is ready to go. Notice that i’ve only chosen text and boolean fields to add to the endpoint. That’s not a comment on the RESTful modules capability more on the format of this blog series (everything in its due time). Also notice that I have not defined a title field. Actually the restful module gives us a couple things for free - like the title and id.
Step 5 - enable the module and visit the API
Now… enable the myresteasy_api module!
Oh, and one more thing: you may need to clear some caches, so if you don’t have drush up and running, maybe now is a good time to start, or head on over to (/admin/config/development/performance).
-
Clear the cache.
-
Visit: /api/v0.1/artists
Here you will see the four artists you entered before:
{
"data": [
{
"id":1,
"label":"The Beatles",
"self":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists\/1",
"yearFormed":"1960",
"countryOfOrigin":"England",
"rockAndRollHallOfFameInductee":true
},
{
"id":2,
"label":"Chuck Berry",
"self":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists\/2",
"yearFormed":"1955",
"countryOfOrigin":"USA",
"rockAndRollHallOfFameInductee":true
},
{
"id":3,
"label":"The Jimi Hendrix Experience",
"self":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists\/3",
"yearFormed":"1966",
"countryOfOrigin":"USA",
"rockAndRollHallOfFameInductee":true
},
{
"id":4,
"label":"My Bloody Valentine",
"self":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists\/4",
"yearFormed":"1983",
"countryOfOrigin":"Ireland",
"rockAndRollHallOfFameInductee":false
}
],
"count":4,
"self":{
"title":"Self",
"href":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists"
}
}
This is what we call an API listing endpoint.
RESTful gives you both a listing endpoint and individual endpoint as well, such as: /api/v0.1/artists/1, which is in the pattern/api/v0.1/artists/{id}.
{
"data": [
{
"id":"1",
"label":"The Beatles",
"self":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists\/1",
"yearFormed":"1960",
"countryOfOrigin":"England",
"rockAndRollHallOfFameInductee":true
}
],
"self":{
"title":"Self",
"href":"http:\/\/resteasy.local.dev\/api\/v0.1\/artists\/1"
}
}
Hey look at that — an API endpoint that will spit out listings of all of your content as well as individual items. It’s really just barely scratching the surface of RESTful and APIs but you gotta start somewhere and with less than 75 lines of custom code you have the a basic version of your future API.
Phew, we made it to the end. Did anybody notice anything missing? Yeah, sure you did. You have an exceptionally good eye. I excluded the body field. Why? Well, because that’s what we’ll be taking a look at next time: fields and subproperties!