Khanh Hoang - Kenn
Kenn is a user experience designer and front end developer who enjoys creating beautiful and usable web and mobile experiences.
You can build a RESTful service with Drupal 7, and do it easily. In this post, I will show an easy way to build a snappy RESTful service, that queries the blog posts by date, so you can get the specified amount of blog posts for the specified amount of days.
For this purpose, we will use Drupal 7 and Services module. We will also write a custom module to handle the back end. There are a few contrib modules that can handle the back end, one being Views Services, but there is certain slowness that comes from using the Views module that we want to evade in cases when we expect large amounts of nodes to be queried. So we will use our own back-end instead.
So, we download and install the Services module and enable the REST Server module that comes together with it. Then, we follow the steps:
Navigate to the Services UI and add a service. Here, we need to set up an endpoint and choose the server. Endpoint, plainly put, is the functionality that handles the request. We will write a module to handle that later. Right now, let’s just name it – blog– and specify the path for it - api/blog. The path is the path to our service. Also, let’s set the server type to REST.
The first step is in the Services module configuration screen, where we add a service name, set up an endpoint and choose the server.
Once you have created your service, it will show in the services list. In the Operations column, select Edit Resources.
Next, select "Edit Resources".
Resources are content available for query though the end points. As you look at the default setup, you see those resources, that come hard-coded with the Services module.
The default setup shows only resources that come hard-coded with the Services module. Our resource is not showing there yet. We will need to create it in a custom module.
Our resource is not showing there yet. We will need to create it in a custom module.
If you don’t know how to write a module, see the Drupal.org module writing tutorial. Here, we will only cover the specifics needed for our RESTful service. For convenience, I will call the module MYMODULE, to make the replaceable parts of code stand out.
Resources are declared via hook_services_resources(). Here is what our code looks like with the new hook:
<?php /** * Implements of hook_services_resources(). */ function MYMODULE_services_resources() { $api = array( 'blog' => array( 'operations' => array( 'retrieve' => array( 'help' => 'Retrieves posted blogs', 'callback' => '_MYMODULE_blog_retrieve', 'access callback' => 'user_access', 'access arguments' => array('access content'), 'access arguments append' => FALSE, 'args' => array( array( 'name' => 'fn', 'type' => 'string', 'description' => 'Function to perform', 'source' => array('path' => '0'), 'optional' => TRUE, 'default' => '0', ), array( 'name' => 'nitems', 'type' => 'int', 'description' => 'Number of latest items to get', 'source' => array('param' => 'nitems'), 'optional' => TRUE, 'default' => '0', ), array( 'name' => 'since', 'type' => 'int', 'description' => 'Posts from the last number of days', 'source' => array('param' => 'since'), 'optional' => TRUE, 'default' => '0', ), ), ), ), ), ); return $api; } ?>
In the code above, we declare a Resource called blog, with a function retrieve. This function will be our first element in path, arg(0). Then, we allow two parameters, nitemsand since, both optional. First specifies the number of items to query, and second - how many days ago to include.
The data will be accessible for a user with access content permission, and the params will be passed to a callback function _MYMODULE_blog_retrieve().
Our callback function:
<?php /** * Callback function for blog retrieve */ function _MYMODULE_blog_retrieve($fn, $nitems, $timestamp) { // Check for mad values $nitems = intval($nitems); $timestamp = intval($timestamp); return MYMODULE_find_blog_items($nitems, $timestamp); } ?>
In this callback function, we don’t do much. First, we sanitize the values. We are going to query the database, so here we at least assure, that both parameters are integer (they will also be sanitized in the Drupal's Database Layer). Then, we pass the parameters to the actual processing function, and return the value. Returned value will be fed to the output of the REST server. (And formatted in a way specified in the Services endpoint settings via the UI.)
The first function argument, $fn, will have the ‘retrieve’ string always in our case.
There is no rule that would make us create a separate function, but I prefer to keep it separate for organizational reasons. In case if no parameters are specified, our service will return all blog posts, which has been tested well performance-wise with as many as 500-600 nodes to return. To keep this process snappy, we use a custom query, rather than using the views module or a node_load to get the fields we need.
<?php /** * Gets blog posts */ function MYMODULE_find_blog_items($nitems, $timestamp) { // Compose query $query = db_select('node', 'n'); $query->join('node_revision', 'v', '(n.nid = v.nid) AND (n.vid = v.vid)'); $query->join('users', 'u', 'n.uid = u.uid'); $query->join('field_data_body', 'b', '((b.entity_type = \'node\') AND (b.entity_id = n.nid) AND (b.revision_id = n.vid))'); $query->fields('v', array('timestamp', 'title')); $query->addField('u', 'name', 'author'); $query->addField('b', 'body_value', 'content'); $query->condition('n.type', 'blog', '='); // How many days ago? if ($timestamp) { $query->condition('v.timestamp', time() - ($timestamp * 60 * 60 * 24), '>'); } $query->orderBy('v.timestamp', 'DESC'); // Limited by items? if ($nitems) { $query->range(0, $nitems); } $items = $query->execute()->fetchAll(); return $items; } ?>
If Drupal dynamic queries look baffling, please refer to the Drupal 7 Dynamic Queries Tutorial. Here, we perform a query, that joins the node_revisiontable, the userstable, and the field_data_bodytable to the nodetable. All this is needed to get the recent node revision’s data and user information. You may want to add or remove fields as you deem needed.
In this case, both our parameters are passed to the dynamic query in a way that allows Drupal to sanitize them. It's very important to keep your service secure against SQL injections.
Now, our resource will be showing in the Services module settings. And the last thing remaining to be done is to enable it.
After enabling the resource, we should be able to query the service by navigating to a path like /api/blog/retrieve?nitems=5&since=10- this should return 5 last blog posts within the last 10 days.
Play more with the Resource settings configuration in the Service UI to select the server options, such as input and output formats.