One of the most anticipated features in Drupal 8 is the integration of RESTful Web Services in Drupal core. Drupal devs are looking forward to being able to do things with core which they couldn't before, such as:
- Offering their site’s data up in an API for others to use and contribute to;
- Building better user interactions by adding and updating content in place instead of a full page submission;
- Developing iPhone and Android apps that can serve and manage content hosted in a Drupal site.
But what are RESTful Web Services? In this article, I will walk you through the different conceptions of what is RESTful and explain how the new modules in Drupal core address these different concepts.
A Quick History of REST
Many developers have become aware of REST due to the rising popularity of APIs. These APIs enable developers to build on top of services such as Twitter and Netflix, and many of these APIs call themselves RESTful. Yet these APIs often work in extremely different ways. This is because there are many definitions of what it means to be RESTful, some more orthodox and others more popular.
The term REST was coined by Roy Fielding, one of the people working on one of the earliest Web standards, HTTP. He coined the term as a description of the general architecture of the Web and systems like it. Since the time he laid out the constraints of a RESTful system in his thesis, some parts have caught hold in developer communities, while others have only found small – but vocal – communities of advocates.
For a good explanation of the different levels of RESTful-ness, see Martin Fowler’s explanation of the Richardson Maturity Model.
What is RESTful?
So what are the requirements for RESTfulness?
Everyone pretty much agrees: Formats and Resources
The most popular understanding of REST includes working with machine readable formats and interacting with resources instead of endpoints.
Most people agree: Using standardized HTTP methods
Most APIs that call themselves RESTful at this point will also use standardized HTTP methods, though this hasn’t always been the case, and some APIs will still call themselves RESTful without doing this.
REST advocates agree: Hypermedia
Finally, one thing that REST advocates will stress the importance of – but which is rarely found in APIs calling themselves RESTful – are hypermedia controls.
Working with Machine Readable Formats
Machine readable formats, such as JSON and XML, are used for any kind of Web Service. Services that call themselves RESTful usually use JSON just because it is a format that is easy to read, both for humans and machines. You’ve probably seen JSON before. Here’s an example of what a very basic node would look like in JSON.
{ "title": "Fancy title" "field_body": "Fancier content." }
In some APIs, you will request the format by appending the format to the end of the filename, e.g. node/1.json
. A more RESTful way to do it is to use HTTP’s built in way of switching between two differently formatted representations, which is called content negotiation.
To do this, an HTTP header is used to specify the media type. For example, to say that you want to receive a JSON version of a node, you would send:
Accept: application/json
In this example, Accept is the header name and application/json is the media type.
Interacting with Resources Rather than Endpoints
Endpoints are used by other styles of Web Services, such as XML-RPC. In services such as these you have a single URI that you interact with. For example, WordPress enables XML-RPC services by default, so if you have a WP site, your API would be available at:
http://example.com/xmlrpc.php
You would then send a request to it which contained the identifier of the post that you wanted to get:
<methodCall> <methodName>wp.getPost</methodName> <params> ... <param> <value> <string>107</string> </value> </param> </params> </methodCall>
In REST, you don’t include the ID in the message body. Instead, the thing that you want to perform the operation on is identified by the URI that you are sending the request to. So instead of sending the request to /xmlrpc.php
, you would send the request to the URI that identifies the thing, e.g. http://example.com/blog/107
.
While some people talk about their REST APIs having endpoints, Roy Fielding has been pretty unambiguous on the fact that REST APIs should have starting points, not endpoints.
Using Standardized Request Methods
Instead of using a custom method, such as wp.getPosts
in the example above, a RESTful service uses standardized request methods. So to get posts, you would send a GET
request. Other standardized request methods which are commonly used include POST
, DELETE
, PUT
, and PATCH
.
The method to use shouldn’t be indicated in the request body (as in XML-RPC). It also shouldn’t be part of the URI. For example, in the early days of Rails’ REST support, the URIs used for GET requests were structured as follows: /posts/show/1
.
If you're interested in how the Rails community interprets REST, Steve Klabnik provides good analysis. There is also a well known post, Getting Hyper about Hypermedia, from Rails lead DHH.
The “show” in the URI is redundant, since the meaning of the request method GET is the same as show.
Making the Next Steps that can be Taken Clear in the Response Itself (a/k/a Hypermedia)
Hypermedia is the most complex and least understood part of making a truly RESTful API. The very basic essence of hypermedia is that you include links to the next steps the user can take in your API, using something called link relations. For example, if you were building a shop RESTfully, when returning a list of items in someone’s cart your API might include the payment link relation to indicate where the user can go to pay for their items.
{ "_links": { "self": {"href": "http://store.com/customers/linclark/cart"}, "payment": {"href": "http://store.com/customers/linclark/cart/pay"} } "items": { ... } }
Because the link relations, such as payment in the above example, are standardized, you shouldn’t need a custom app to interact with them. Instead, it should be possible to browse an API and use it with a generic app. In the above instance, the snippet is HAL (Hypertext Application Language – more of that to follow), so you would be able to interact with it using the HAL Browser. For example, FoxyCart uses the HAL Browser as a generic UI on their API.
You might see people use the term HATEOAS, which stands for Hypermedia as the Engine of Application State. The idea behind HATEOAS is that you only need to have the URL of a starting point of an API, you shouldn’t hardcode URIs from another system directly in to your app. Once you’re at the starting point, you can follow the link relations to traverse the different resources and step through different processes, much as a human does when seeking information by following links.
This aspect of RESTfulness is the one that has the least short-term benefit, because few clients are set up to make full use of the benefits of hypermedia. It also has the most upfront cost because it requires that you have a developer or team that both understands your domain and also understands hypermedia and link relations. And in cases where your domain is not well standardized, it requires a standardization effort to make it really useful.
How Drupal’s new Features Support the Different Parts of RESTfulness
Machine Readable Formats
With changes made to the core routing system and the introduction of serialization module, Drupal is now format-aware. The system can serialize content entities – such as nodes and comments – to different data formats. It can also deserialize from some data formats back to entities, though not all data formats support deserialization.
Serialization module supports JSON and XML, though the XML support is quite limited without any support for XML features such as namespaces and attributes. Additionally, the core module HAL, referred to earlier, implements the Hypertext Application Language.
The system can be easily extended to support other formats. For example, we could introduce support for GitHub’s custom JSON media type.
- Register the media type with the system. This can be done by subscribing to the onKernelRequest event and then making a call to
Request::setFormat()
.$request->setFormat('github_json', ‘application/vnd.github.v3+json'); - Add Normalizer classes to convert an entity object into an array. These classes should implement the
NormalizerInterface
. You can optionally support theDenormalizerInterface
too, if you want to be able to process serialized data back into an entity. - Add an Encoder class. This will take the normalized array and encode it into a string. The Encoder class should implement the
EncoderInterface
, and optionally theDecoderInterface
. - Register your classes with the system using a
services.yml
file. You will tag your classes with the “normalizer” tag, and they will automatically be added to the serializer.
Additionally, the system can easily be extended to support non-entity data structures. You simply need to add Normalizers that return true to supportsNormalization
for those classes.
Resources
REST module provides resource-centric services for entities. Whenever you create a new entity type, you will automatically get a new resource to match.
These resources still need to be enabled in order to work. To do this, add an entry in rest.settings.yml
. By default, the resource for nodes is enabled and can be used as an example configuration.
You can also add your own resources for things besides entities. Each resource is provided as a plugin. Take a look at the DBLogResource
, which provides a simple example of a non-entity resource plugin.
Request methods
The entity resources provided by REST module support the following request methods:
GET | Get a serialized representation of an entity |
POST | Create a new entity |
PATCH | Update specific fields on an existing entity |
DELETE | Remove an entity from the database |
In a fully RESTful system, the POST operation to create new entities would actually be a different resource as it is identified by a separate URI.
You can find the code for handling these methods on the EntityResource class, e.g. EntityResource::delete($id)
.
To add support for a new method to a resource, simply add a matching method to the class: if we wanted to support the HEAD
method, we would simply add a head()
method to the resource class.
By default, permissions to interact with a resource have to be granted on a per method basis, so you will need to configure the permissions for whatever methods you add.
Hypermedia
As mentioned, Drupal has a hypermedia format in core, HAL. This is a lightweight way of adding link relations to a data format, and it can be used with either JSON or XML. Drupal core supports only the JSON version.
The HAL normalizer exposes all entity references, images, and other links as link relations, grouped together under the _links
property. By default, it uses a URI generated by the site as the link relation for each field. For example, the tags field on an article is: http://example.com/rest/relation/node/article/field_tags
However, this doesn’t really provide the benefits of hypermedia. Hypermedia is useful when generic clients can be built which aren’t tied to a specific implementation. This benefit comes from using standardized link relations, not link relations which are custom to each site. The next-archive and prev-archive link relations were standardized in the Feed Paging and Archiving RFC. When these are used, tools that are aware of these link relations can page through results.
To use a different set of link relations from the default, replace the LinkManager classes.
Will Your Services be Fully RESTful?
So, will your Drupal 8 site’s API fully pass the REST test? It is unlikely... but that’s not a problem.
It’s important to understand the different things that different people mean when they talk about RESTfulness and what advantages it brings. Many of the tools and services that call themselves RESTful actually follow Web API conventions, which on the one hand often fall short of RESTfulness and on the other hand often depend on conventions which aren’t part of REST at all and actually violate the REST constraints.
All in all, site developers are best served by considering which consumers they want to target with their services, and what those consumers expect. This may require some customization, but hopefully we’ve developed a framework that will provide the capabilities you need for meeting your consumer’s expectations.
Image: ©iStockphoto.com/maxkrasnov