- Module Options
- Why Restful
- Install & configure Restful
- Create Endpoints
- Single node endpoint
- Node collection endpoint
- Filtering content
- Referenced content (node author)
- Handling Media
- Queued Content
- Hypermedia API Design in practice
- Apiary for mocking & Docs.
- Mocking in Drupal directly.
- Drupal Overview
-
Services
- Oldest module
- Good architecture for setting up servers
- Handles different servers becides REST
- Horrible default Entity endpoints
- Custom endpoint creation is not intuitive and hard to work with
-
RESTws
- Not a bad option overall
- Custom endpoint creation is OK
- Less robust server implementation
- Doesn't do versioning
-
RESTful
- The module we are using for this class
- Very good API for creating endpoints
- Very well written.
- No default endpoints
- Designed from the ground up to create beautiful APIs
This module allows Drupal to be operated via RESTful HTTP requests, using best practices for security, performance, and usability.
- All endpoints are explicitly created. The module does nothing by default.
- This allows for clean output and keeps Drupalisms from leaking out into your API
- Versioning is core to how endpoints are built
- Endpoints are built around bundles, not entity types, making it easy to expose certain endpoints, but not others.
- Configurable output formats allowing for output in JSON or XML
- Object oriented architecture that makes it easy to created endpoints and reuse code.
- Very good test coverage in the module.
The content we'll be working with is populated with posts from the four kitchens blog. Below is a list of the fields that we'll be working with for easier reference. The login to your Database is admin:admin.
Field Label | Machine Name | Field Type | Comments |
---|---|---|---|
Lead image | field_lead_image | Image | |
Body | body | Long Text | |
Inline Images | field_inline_image | Image | |
Files | field_blog_files | File | |
Blog Categories | field_blog_categories_term_tree | Term reference | |
Blog Series | field_blog_series_term_tree | Term reference |
The module is already installed. The only dependency is the Entity API module. There is one patch to the Entity API module required.
Once the module is installed there is no exposed admin page. You need to create your own module that declares a Restful plugin to use the module.
Lets do that now!
We skipped the normal setup of a Drupal module, including creating the .info file and in the module folder we declared where ctools should look for restful plugins. The 'plugins/' folder in this module.
restful_fourword/restful_fourword.module
<?php
/**
* Implements hook_ctools_plugin_directory().
*/
function restful_fourword_ctools_plugin_directory($module, $plugin) {
if ($module == 'restful') {
return 'plugins/' . $plugin;
}
}
The custom module is already created and you can start working with it. The repo for it has Git tags for each lesson. You can always see what the end result of a lesson is and check if you are on the righ track by using a simple diff command.
To get started checkout a branch that is based on the lesson_1_start tag.
git checkout -b lesson1 lesson_1_start
You can now make changes to your code. To compare your code to the end of the lesson use the following command:
git diff lesson_1_end
As you can see we are going to add a file "plugins/restful/node/blog_posts/1.0/blogPosts__1_0.inc"
Lets get started.
restful_fourword/plugins/restful/node/blog_posts/1.0/blogPosts__1_0.inc
<?php
$plugin = array(
'label' => t('Blog Posts'),
'resource' => 'articles',
'name' => 'blogPosts__1_0',
'entity_type' => 'node',
'bundle' => 'blog_post',
'description' => t('Fourword blog posts using view modes'),
'class' => 'RestfulEntityBaseNode',
'authentication_types' => TRUE,
'authentication_optional' => TRUE,
'view_mode' => array(
'name' => 'default',
'field_map' => array(
'body' => 'body',
'field_lead_image' => 'image',
'field_blog_categories_term_tree' => 'categories'
),
),
);
The resource key determines the root URL of the resource. The name key must match the filename of the plugin: in this case, the name is blogPosts__1_0
, and therefore, the filename is blogPosts__1_0.inc
.
This endpoint is created using the default view mode, and exposes any fields that are listed in the field_map
array. Note: Fields must be visible in the view mode for them to show up.
Lets talk about the different parts of the array.
label
- The label of the plugin.resource
- Name of the resource, must match the filename of the plugin.name
- Machine name of the plugin.entity_type
- Type of Entity this resource will expose.bundle
- Name of the bundle this resource will expose.description
- Longer description of the plugin.class
- Class file that will declare the plugin.authentication_types
- What type of authentication this plugin will use.authentication_optional
- Determines if the authentication is required.minor_version
- Minor version of the API that this plugin will be exposed under.view_mode
- Array with info about the view mode to use for this endpoint.
RESTful comes with a bunch of out of the box functionality for any resource that you create. Including:
- See a paginated list of blog posts, and use hyper links to go to different pages:
api/articles
- Get a specific blog post:
api/articles/29,143
- Limit the fields returned using the fields= query parameter
api/articles/29?fields=body
- Filter the API using the filter[lable]= query parameter.
- More info on consuming the API is in (this file)[https://github.com/RESTful-Drupal/restful/blob/7.x-1.x/docs/api_url.md].
If an error occurs when operating the REST endpoint via URL, A valid JSON object with code
, message
and description
would be returned.
The RESTful module adheres to the Problem Details for HTTP APIs draft to improve DX when dealing with HTTP API errors.
For example, trying to sort a list by an invalid key
curl http://finished.drupal.headless.4kclass.com:8080/api/articles?sort=wrong_key
Will result with an HTTP code 400, and the following JSON:
{
'type' => 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1',
'title' => 'The sort wrong_key is not allowed for this path.',
'status' => 400,
'detail' => 'Bad Request.',
}
Add a new key to the view_modes
key that is field_blog_categories_term_tree
with a value of categories
. Go to the interface and add the blog categories to the default view mode.
This isn't output isn't great. The HTML surrounding the categories is going to make life hard for our API consumers. We could install (Display Suite)[https://www.drupal.org/project/ds], but even with those tools we are going to hit a wall sometime or another. Lets try creating a more custom endpoint instead.
git add .
git commit -am "end of lesson1"
git checkout -b lesson2 lesson_2_start
Lets create a new version of our API. Once your API is published to the world it's important to be diligent about versioning. There is nothing worse than having your site broken by an API that changed. OK, having your site broken on a Friday by a broken API is worse.
Luckily RESTful is built from the ground up to facilitate easy versioning.
- Copy the 1.0 folder to a 1.1 folder.
- Rename the
blogPosts__1_0.inc
file toblogPosts__1_1.inc
- Add a
minor_version
key with a value of 1 to the plugin definition. - Clear the cache
drush cc all
- Visit the new version
/api/v1.1/articles
You can access different versions of the API using two different methods.
- Using the URL. Simply replace the
v1.1
withv1.0
. - Using a header.
curl http://finished.drupal.headless.4kclass.com:8080/api/articles \
-H "X-API-Version: v1.0"
The URL is useful for humans, but the header is useful for building API clients.
Using the view modes is fine, but the real power of Restful comes from creating custom endpoints. We'll make one now. You can save your work in this branch and create a new branch from the lesson 2 starting point.
git add .
git commit -am "end of lesson2"
git checkout -b lesson3 lesson_3_start
- Copy the 1.1 folder to a 1.2 folder.
- Rename the
blogPosts__1_1.inc
file toblogPosts__1_2.inc
- Add a
minor_version
key with a value of 2 to the plugin definition. - Add the following class to
blogPosts__1_2.inc
/**
* @file
* Contains RestfulFourwordBlogPostsResource__1_2.
*/
class RestfulFourwordBlogPostsResource__1_2 extends RestfulEntityBaseNode {
public function publicFieldsInfo() {
$public_fields = parent::publicFieldsInfo();
$public_fields['body'] = array(
'property' => 'body',
'sub_property' => 'value',
);
return $public_fields;
}
}
In the plugin definition, the class
value to RestfulFourwordBlogPostsResource__1_2
.
'class' => 'RestfulFourwordBlogPostsResource__1_2',
Finally, remove the view_mode
key; it's no longer needed with the class! The final result:
$plugin = array(
'label' => t('Blog Posts'),
'resource' => 'articles',
'name' => 'blogPosts__1_2',
'entity_type' => 'node',
'bundle' => 'article',
'description' => t('Fourword blog posts using view modes'),
'class' => 'RestfulFourwordBlogPostsResource__1_2',
'authentication_types' => TRUE,
'authentication_optional' => TRUE,
'minor_version' => 2,
);
This is all the code you need to create your new endpoint.
- Clear the cache
drush cc all
- Visit the endpoint, which will be available at
/api/v1.2/articles
It does the following:
- This extends the class
RestfulEntityBaseNode
. This is a convience class that makes working with entities easier. If you wanted to create an endpoint that exposed something else you would use a different base class. - Creates a public function
publicFieldsInfo()
. - In that function creates a variable called
$public_fields
populated with the parent'spublicFieldsInfo()
- Adds a new field called body and sets the body to the value of the nodes body field.
The same technique can be used to add any text or numerical fields as properties to your API, but what if you want to add links to other resources? For example lets say we created and endpoint that is for categories, and wanted to link to all the categories that each blog post has?
Hey look at that someone created a categories endpoint for us: /api/articles
. Open up the folder and take quick look, we didn't need to do anything besides declare the class.
Add the following after the body property. This will create a link to the blog categories which is a link to the articles
endpoint.
$public_fields['categories'] = array(
'property' => 'field_blog_categories_term_tree',
'resource' => array(
'blog_categories' => 'blog_categories',
),
);
$public_fields['lead_image'] = array(
'property' => 'field_lead_image',
);
That works, but we are leaking lots of info our API consumers don't need, plus there isn't a simple link to the image. Lets fix that with a custom processor.
RESTful gives us an easy way to process any callback.
Add the following method to our class.
/**
* Process callback, Remove Drupal specific items from the image array.
*
* @param array $value
* The image array.
*
* @return array
* A cleaned image array.
*/
protected function fourwordImageProcess($value) {
return array(
'id' => $value['fid'],
'self' => file_create_url($value['uri']),
'filemime' => $value['filemime'],
'filesize' => $value['filesize'],
'width' => $value['width'],
'height' => $value['height'],
'styles' => $value['image_styles'],
);
}
The process function accepts a Drupal field output array, and should return an array of mapped keys and values.
We use this method on our resource by adding a process_callbacks
array to the lead_image
public_field. Add the following code below the property
key.
'process_callbacks' => array(
array($this, 'fourwordImageProcess'),
),
Finally there is a neat little function that will add image styles. Add the following after the process_callbacks
.
'image_styles' => array('thumbnail', 'medium', 'large'),
Let's wrap up by putting this latest work into our git repository.
git add .
git commit -am "end of lesson3"
- Silver: Add a resource for the series by copying the categories endpoint and add a new property called series using the
field_blog_series_term_tree
property to link to it like categories. - Gold: Create an endpoint that links to the user API resource who authored the blog post.
- Platinum: Create a new endpoint that exposes the sites variables see (this example)[https://github.com/RESTful-Drupal/restful/blob/7.x-1.x/modules/restful_example/plugins/restful/variable/1.0/]