Over the past few months (since Drupalcon LA), I have been actively diving in to the world of decoupled Drupal, in particular using AngularJS as a front end. After going through many tutorials and looking at the multiple options for providing an API from Drupal to be consumed by Angular, I settled on using the RESTful module. One thing I like in particular about it is that you have to explicitly define what you want available in your API, as compared to everything being automatically being made available when the module is enabled.
In addition to the documention in the GitHub wiki and on drupal.org, there is also a really great YouTube video series on how to write your custom classes and perform the tasks you need to do to comsume the API, such as sorting, filtering, and displaying related content.
Clean URL Mapping
In Drupal, the most common method of creating URLs is to use clean URLs to access data, instead of using the base /node/$nid URL. But, since Drupal uses the base URL for loading the node data, there has to be mapping and lookup functionality for linking the clean URL with the base URL. This is handled with the core Path module, with the mappings being stored in the url_alias table. When a page is accessed with a clean URL, the url_alias table is queried to get the base URL, the node is loaded, and the beat goes on.
However, in looking through all of the different API options and tutorials, I didn't see this clean URL lookup functionality (although I didn't look incredibly hard, so I might have missed something, too). Everything I saw was based on passing an entity ID in a GET URL. Even if I'm using a decoupled front end, I still want to use a clean URL, so I need to be able to take that clean URL, query my API to get the entity id using the clean URL as the search criteria, and then use that value to load the whole entity.
In the process of my searching, I discovered a wiki page that showed that RESTful has a built in class (a data provider in RESTful lingo) that allows you to query a database table directly. This was exactly what I needed.
The Code
Creating a RESTful plugin for querying a table is very similar to creating a plugin for getting entity information; you have to first define your plugin, and then define your public fields (the fields you want available as part of the API).
Here's the final code for my plugin:
/** * Contains \Drupal\restful_tutorial\Plugin\resource\url_alias */ namespace Drupal\restful_tutorial\Plugin\resource\url_alias; use Drupal\restful\Plugin\resource\ResourceDbQuery; use Drupal\restful\Plugin\resource\ResourceInterface; /** * Class URLAlias__1_0 * @package Drupal\restful_tutorial\Plugin\resource\url_alias * * @Resource( * name = "urlalias:1.0", * resource = "urlalias", * label = "URL Alias", * description = "Gets the entity id from a URL alias.", * authenticationTypes = TRUE, * authenticationOptional = TRUE, * dataProvider = { * "tableName": "url_alias", * "idColumn": "alias", * "primary": "pid", * "idField": "alias", * }, * majorVersion = 1, * minorVersion = 0, * class = "URLAlias__1_0" * ) */ class URLAlias__1_0 extends ResourceDbQuery implements ResourceInterface { /** * {@inheritdoc} */ protected function publicFields() { $public_fields['pid'] = array( 'property' => 'pid' ); $public_fields['source'] = array( 'property' => 'source' ); $public_fields['alias'] = array( 'property' => 'alias' ); return $public_fields; } }
Here are the key points from the code:
- The annotations are what provides the details about the table source.
- The idColumn value is the database field to which the value passed in the URL will be compared.
I then query the api endpoint with the following URL:
http://mysite/api/urlalias/steve-edwards/2015/12/05/my-blog-title
and the resulting JSON output looks like this:
{ "data": { "type": "urlalias", "id": "154177", "attributes": { "pid": "154177", "source": "node/97783", "alias": "steve-edwards/2015/12/05/my-blog-title" }, "links": { "self": "http://mysite/api/v1.0/urlalias/154177" } }, "links": { "self": "http://mysite/api/v1.0/urlalias/steve-edwards/2015/12/05/my-blog-title" } }
From there, you can get the id value, and then call your appropriate endpoint to get the full entity.
This is a pretty basic use of the DataProviderDbQuery class, but it's a very powerful tool that allows you to create and expose custom queries as a RESTful API endpoint as needed for a decoupled setup.