An introduction to Drupal 7 RESTful Services
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.
Photo: ChrisDag http://www.flickr.com/photos/chri...
So, we download and install the Services module and enable the REST Server module that comes together with it. Then, we follow the steps:
1. Add a Service in Services UI.
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.
2. Open a Resources page.
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.
3. Create an empty 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.
4. Declare a Services Resource.
Resources are declared via hook_services_resources()
. Here is what our code looks like with the new hook:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implements of hook_services_resources().<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">MYMODULE_services_resources</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$api </span><span style="color: #007700">= array(<br> </span><span style="color: #DD0000">'blog' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'operations' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'retrieve' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'help' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Retrieves posted blogs'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'callback' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'_MYMODULE_blog_retrieve'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'access callback' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'user_access'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'access arguments' </span><span style="color: #007700">=> array(</span><span style="color: #DD0000">'access content'</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'access arguments append' </span><span style="color: #007700">=> </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'args' </span><span style="color: #007700">=> array(<br> array(<br> </span><span style="color: #DD0000">'name' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'fn'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'string'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'description' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Function to perform'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'source' </span><span style="color: #007700">=> array(</span><span style="color: #DD0000">'path' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'optional' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'default' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> ),<br> array(<br> </span><span style="color: #DD0000">'name' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'nitems'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'int'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'description' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Number of latest items to get'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'source' </span><span style="color: #007700">=> array(</span><span style="color: #DD0000">'param' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'nitems'</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'optional' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'default' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> ),<br> array(<br> </span><span style="color: #DD0000">'name' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'since'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'int'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'description' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Posts from the last number of days'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'source' </span><span style="color: #007700">=> array(</span><span style="color: #DD0000">'param' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'since'</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'optional' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'default' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> ),<br> ),<br> ),<br> ),<br> ),<br> );<p> return </p></span><span style="color: #0000BB">$api</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span>
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, nitems
and 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()
.
5. Create the callback function.
Our callback function:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Callback function for blog retrieve<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">_MYMODULE_blog_retrieve</span><span style="color: #007700">(</span><span style="color: #0000BB">$fn</span><span style="color: #007700">, </span><span style="color: #0000BB">$nitems</span><span style="color: #007700">, </span><span style="color: #0000BB">$timestamp</span><span style="color: #007700">) {<br> </span><span style="color: #FF8000">// Check for mad values<br> </span><span style="color: #0000BB">$nitems </span><span style="color: #007700">= </span><span style="color: #0000BB">intval</span><span style="color: #007700">(</span><span style="color: #0000BB">$nitems</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$timestamp </span><span style="color: #007700">= </span><span style="color: #0000BB">intval</span><span style="color: #007700">(</span><span style="color: #0000BB">$timestamp</span><span style="color: #007700">);<p> return </p></span><span style="color: #0000BB">MYMODULE_find_blog_items</span><span style="color: #007700">(</span><span style="color: #0000BB">$nitems</span><span style="color: #007700">, </span><span style="color: #0000BB">$timestamp</span><span style="color: #007700">);<br>}<br></span><span style="color: #0000BB">?></span></span>
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.
6. Create the processing function.
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.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Gets blog posts<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">MYMODULE_find_blog_items</span><span style="color: #007700">(</span><span style="color: #0000BB">$nitems</span><span style="color: #007700">, </span><span style="color: #0000BB">$timestamp</span><span style="color: #007700">) {<br> </span><span style="color: #FF8000">// Compose query<br> </span><span style="color: #0000BB">$query </span><span style="color: #007700">= </span><span style="color: #0000BB">db_select</span><span style="color: #007700">(</span><span style="color: #DD0000">'node'</span><span style="color: #007700">, </span><span style="color: #DD0000">'n'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">join</span><span style="color: #007700">(</span><span style="color: #DD0000">'node_revision'</span><span style="color: #007700">, </span><span style="color: #DD0000">'v'</span><span style="color: #007700">, </span><span style="color: #DD0000">'(n.nid = v.nid) AND (n.vid = v.vid)'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">join</span><span style="color: #007700">(</span><span style="color: #DD0000">'users'</span><span style="color: #007700">, </span><span style="color: #DD0000">'u'</span><span style="color: #007700">, </span><span style="color: #DD0000">'n.uid = u.uid'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">join</span><span style="color: #007700">(</span><span style="color: #DD0000">'field_data_body'</span><span style="color: #007700">, </span><span style="color: #DD0000">'b'</span><span style="color: #007700">, </span><span style="color: #DD0000">'((b.entity_type = \'node\') AND (b.entity_id = n.nid) AND (b.revision_id = n.vid))'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">fields</span><span style="color: #007700">(</span><span style="color: #DD0000">'v'</span><span style="color: #007700">, array(</span><span style="color: #DD0000">'timestamp'</span><span style="color: #007700">, </span><span style="color: #DD0000">'title'</span><span style="color: #007700">));<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">addField</span><span style="color: #007700">(</span><span style="color: #DD0000">'u'</span><span style="color: #007700">, </span><span style="color: #DD0000">'name'</span><span style="color: #007700">, </span><span style="color: #DD0000">'author'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">addField</span><span style="color: #007700">(</span><span style="color: #DD0000">'b'</span><span style="color: #007700">, </span><span style="color: #DD0000">'body_value'</span><span style="color: #007700">, </span><span style="color: #DD0000">'content'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">condition</span><span style="color: #007700">(</span><span style="color: #DD0000">'n.type'</span><span style="color: #007700">, </span><span style="color: #DD0000">'blog'</span><span style="color: #007700">, </span><span style="color: #DD0000">'='</span><span style="color: #007700">);<br> </span><span style="color: #FF8000">// How many days ago?<br> </span><span style="color: #007700">if (</span><span style="color: #0000BB">$timestamp</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">condition</span><span style="color: #007700">(</span><span style="color: #DD0000">'v.timestamp'</span><span style="color: #007700">, </span><span style="color: #0000BB">time</span><span style="color: #007700">() - (</span><span style="color: #0000BB">$timestamp </span><span style="color: #007700">* </span><span style="color: #0000BB">60 </span><span style="color: #007700">* </span><span style="color: #0000BB">60 </span><span style="color: #007700">* </span><span style="color: #0000BB">24</span><span style="color: #007700">), </span><span style="color: #DD0000">'>'</span><span style="color: #007700">);<br> }<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">orderBy</span><span style="color: #007700">(</span><span style="color: #DD0000">'v.timestamp'</span><span style="color: #007700">, </span><span style="color: #DD0000">'DESC'</span><span style="color: #007700">);<br> </span><span style="color: #FF8000">// Limited by items?<br> </span><span style="color: #007700">if (</span><span style="color: #0000BB">$nitems</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">range</span><span style="color: #007700">(</span><span style="color: #0000BB">0</span><span style="color: #007700">, </span><span style="color: #0000BB">$nitems</span><span style="color: #007700">);<br> }<br> </span><span style="color: #0000BB">$items </span><span style="color: #007700">= </span><span style="color: #0000BB">$query</span><span style="color: #007700">-></span><span style="color: #0000BB">execute</span><span style="color: #007700">()-></span><span style="color: #0000BB">fetchAll</span><span style="color: #007700">();<p> return </p></span><span style="color: #0000BB">$items</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span>
If Drupal dynamic queries look baffling, please refer to the Drupal 7 Dynamic Queries Tutorial. Here, we perform a query, that joins the node_revision
table, the users
table, and the field_data_body
table to the node
table. 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.
7. Enable the resource.
Now, our resource will be showing in the Services module settings. And the last thing remaining to be done is to enable it.
The last step is to enable the resource that you have defined.
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.