Restful web services in Drupal 7

Submitted by fago on Mon, 01/31/2011 - 14:07

During the work on my thesis over the last year, I played around a lot with RESTful services based upon the Entity API. What I needed was a simple service that just exposes Drupal's entities in a RESTful manner, while obeying Drupal's permission and access systems. Now, me and klausi have created a small module that does exactly that: Restful web services.

So how does it work?

The module makes use of the Entity API and the information about entity properties (provided via hook_entity_property_info()) to provide resource representations for all entity types (nodes, comments, users, taxonomy terms, ..). It aims to be fully compliant to the REST principles. Drupal's entities are exposed at the unified $entity_type/$id paths, while respecting the Content Accept/Content Type headers of the HTTP requests. That means if a client requests node/1 with usual HTTP accept headers it will get Drupal's usual output, if it requests node/1 while accepting only JSON, it will get the JSON representation of the node. Similarly, all CRUD operations are supported as common for RESTful services. Then, the module supports GET requests on paths like node/1.json, node/1.xml or node/1.rdf too.

And authentication...?

As mentioned above, the solution just obeys Drupal's permission and access system. If there is an active session and the user has sufficient permission for the request, it will be served. So any add-on authentication strategies would have to plug into Drupal's usual user system. For example, the RestWS module comes with a small add-on module that authenticates users via HTTP basic authentication. So you can define a regular user for a client, configure their access permissions as usual, and just pass its credentials with a request.

So what about the property information?

The module makes use of the property information the entity API collects for all entity types, as well as the accompanying wrapper classes. While the API also allows providing non-entities as resources, it requires the existence of property information. Representations of entities are provided according to their property information. What does that mean?
So let's have a look at an example: The node author. In the property information about nodes, there is no uid property, instead there is an 'author' property, pointing to the according user entity. So the module makes use of that information to output a proper reference to the author, being the author's URI (URIs are the proper way to do references in RESTful designs). So instead of just outputting user id as uid property with an integer value, we output a proper reference to the node's author. Apart from that, the property information includes access permissions - so updating the node author will only be possible if you have sufficient permissions.
Then the property information could be used to provide a description of the web service for the caller, in a human as well as in a machine-readable way.

Which formats are supported?

The module currently comes with support for JSON, XML and RDF/XML whereas modules may add more formatters. As the property information is available to the formatters too, it's possible to do formatters that output some properties in a certain way, e.g. using a special XML namespace. Similarly the RDF formatter looks up the RDF mapping being defined for a property, in order to generate meaningful RDF output.

What's different to the Services module?

The main differences are:

* RestWS provides only RESTful services (no message-oriented or RPC-style web services like SOAP, XML-RPC etc.).
* RestWS strongly builds upon the Entity API and its property information, thus utilizes it for CRUD, access checks, getting property information, ..
* Property information is built into the API, so formatters may make use of it to format the data in a sensible way.
* There are no "service endpoints" to configure as resources are just available at uniform paths like node/1, user/1. We do not see a need to have multiple endpoints for the same resource in a RESTful desgin.

For more about the relation and partial overlap to the Services module, read and participate in the discussion over at http://drupal.org/node/1042512.

Example output

You might be interested in the output, so here is the output the module currently produces for a testing-node:

JSON:

{
   "nid":"3",
   "vid":"3",
   "is_new":false,
   "type":"article",
   "title":"asdfdsf",
   "language":"und",
   "url":"https:\/\/example.com\/node\/3",
   "edit_url":"https:\/\/example.com\/node\/3\/edit",
   "status":"1",
   "promote":"1",
   "sticky":"0",
   "created":"1294913241",
   "changed":"1296405309",
   "author":{
      "uri":"https:\/\/example.com\/user\/1",
      "id":"1",
      "resource":"user"
   },
   "log":"",
   "revision":null,
   "comment":"2",
   "comment_count":"0",
   "comment_count_new":"0",
   "body":{
      "value":"\u003cp\u003etest2\u003c\/p\u003e\n",
      "summary":"\u003cp\u003eha\u003c\/p\u003e\n",
      "format":"filtered_html"
   },
   "field_tags":[

   ],
   "field_image":[

   ],
   "field_test":"1",
   "field_file":[

   ]
}

The same node in XML:

<?xml version="1.0" encoding="utf-8"?>
<node>
  <nid>3</nid>
  <vid>3</vid>
  <is_new/>
  <type>article</type>
  <title>asdfdsf</title>
  <language>und</language>
  <url>https://example.com/node/3</url>
  <edit_url>https://example.com/node/3/edit</edit_url>
  <status>1</status>
  <promote>1</promote>
  <sticky>0</sticky>
  <created>1294913241</created>
  <changed>1296405309</changed>
  <author resource="user" id="1">https://example.com/user/1</author>
  <log/>
  <revision/>
  <comment>2</comment>
  <comment_count>0</comment_count>
  <comment_count_new>0</comment_count_new>
  <body>
    <value>&lt;p&gt;test2&lt;/p&gt;

</value>
    <summary>&lt;p&gt;ha&lt;/p&gt;
</summary>
    <format>filtered_html</format>
  </body>
  <field_tags/>
  <field_image/>
  <field_test>1</field_test>
  <field_file/>
</node>

And finally in RDF/XML:

<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Description xmlns:site="https://example.com/" xmlns:dc="http://purl.org/dc/terms/" xmlns:sioc="http://rdfs.org/sioc/ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:og="http://ogp.me/ns#" rdf:about="https://example.com/node/3">
    <rdf:type rdf:resource="http://rdfs.org/sioc/ns#Item"/>
    <rdf:type rdf:resource="http://xmlns.com/foaf/0.1/Document"/>
    <site:nid xmlns:site="https://example.com/">3</site:nid>
    <site:vid xmlns:site="https://example.com/">3</site:vid>
    <site:is_new xmlns:site="https://example.com/"/>
    <site:type xmlns:site="https://example.com/">article</site:type>
    <dc:title xmlns:dc="http://purl.org/dc/terms/">asdfdsf</dc:title>
    <site:language xmlns:site="https://example.com/">und</site:language>
    <site:url xmlns:site="https://example.com/">https://example.com/node/3</site:url>
    <site:edit_url xmlns:site="https://example.com/">https://example.com/node/3/edit</site:edit_url>
    <site:status xmlns:site="https://example.com/">1</site:status>
    <site:promote xmlns:site="https://example.com/">1</site:promote>
    <site:sticky xmlns:site="https://example.com/">0</site:sticky>
    <dc:date xmlns:dc="http://purl.org/dc/terms/" rdf:datatype="xsd:dateTime">1294913241</dc:date>
    <dc:modified xmlns:dc="http://purl.org/dc/terms/" rdf:datatype="xsd:dateTime">1296405309</dc:modified>
    <site:author xmlns:site="https://example.com/">
      <rdf:Description rdf:about="https://example.com/user/1"/>
    </site:author>
    <site:log xmlns:site="https://example.com/"/>
    <site:revision xmlns:site="https://example.com/"/>
    <site:comment xmlns:site="https://example.com/">2</site:comment>
    <sioc:num_replies xmlns:sioc="http://rdfs.org/sioc/ns#" rdf:datatype="xsd:integer">0</sioc:num_replies>
    <site:comment_count_new xmlns:site="https://example.com/">0</site:comment_count_new>
    <content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/">
      <rdf:Description>
        <site:value xmlns:site="https://example.com/">&lt;p&gt;test2&lt;/p&gt;

</site:value>
        <site:summary xmlns:site="https://example.com/">&lt;p&gt;ha&lt;/p&gt;
</site:summary>
        <site:format xmlns:site="https://example.com/">filtered_html</site:format>
      </rdf:Description>
    </content:encoded>
    <dc:subject xmlns:dc="http://purl.org/dc/terms/">
      <rdf:Description/>
    </dc:subject>
    <og:image xmlns:og="http://ogp.me/ns#">
      <rdf:Description>
        <site:alt xmlns:site="https://example.com/"/>
      </rdf:Description>
    </og:image>
    <site:field_test xmlns:site="https://example.com/">1</site:field_test>
    <site:field_file xmlns:site="https://example.com/">
      <rdf:Description/>
    </site:field_file>
  </rdf:Description>
</rdf:RDF>

Greg (not verified)

Mon, 01/31/2011 - 15:11

This looks very VERY cool... light API for sites that just want to expose content quickly and easily. Obvious feature request would be collections of entities via Views (perhaps a Views Display). But with that there should be no other features... just a really light Services alternative. I *love* this. =)

Steven (not verified)

Mon, 03/07/2011 - 17:19

Hi - nice module. I installed it on a fresh Drupal to see if I could use it to consume entities from a separate webapp. It seems the parameters after the .json in the request cause the normal HTML page to be returned rather than the object definition wrapped in a callback. Do I need to add something to the module code to get this to work? or is it already there? or is this not the right module for doing JSONP? any help getting this to work is greatly appreciated.

Nicolas (not verified)

Tue, 06/07/2011 - 22:12

I find your module very interested and well written. I have been working with it couple of days and I'm wondering how can I read a tree of nodes in JSON, is it possible to retrieve more than one node? something like: $entity_type/$id1 $id2 $id3.json? Good Job! Gracias!

Rimbuaj (not verified)

Tue, 07/05/2011 - 16:03

Hey, ¿what about image fields?, it could show the url or something?

moltra (not verified)

Fri, 02/24/2012 - 21:13

I am trying to use your module to get a list of terms from a Taxonomy list. Then this list will be used to select one term and pull a list of nodes with that term and fields. Can I do this with your module? Also does this module work with the drupal search? The site I am hoping to use it with is www.serviidb.com

Michée (not verified)

Sat, 05/19/2012 - 10:58

I am wondering if is there a RESt Client module to receive these entities in another Drupal sites. I would like to share my entities between Drupal sites like that.

Michée (not verified)

Sat, 05/19/2012 - 10:58

I am wondering if is there a RESt Client module to receive these entities in another Drupal sites. I would like to share my entities between Drupal sites like that.

charubachi (not verified)

Wed, 03/26/2014 - 07:57

Hi, in the above you showed us what is the the output of the service. How can I access such kind of data (in json format) and update my nodes.

Frank Pfabigan (not verified)

Mon, 09/15/2014 - 14:01

i'm trying to build multiple sites for a company, where nodes from central will have to be present also in sub-websites, like news and products. there are a lot of different and similiar solutions out there for drupal to provide REST, XLMRPC (...) services. RestWS is by far the simplest solution i found to "spread content out" from the central website. but: all of these solutions don't go the last step. how will this automatically served content make it into nodes (update and create new, if needed). i thought i solved this with feeds module, but whenever i try to fetch a nodes collection of a certain type, the xpath parser of feeds says "no new content". i think it doesn't work because it is not a "well formatted xml"... not easy to say, because the "debug mode" tells nothing. fetching the same content via RSS (served by a view from "central website") works. what a lot of users and webite builders would love to have is a simple solution to "bring that served content" easily into other websites. in my opion a simple (easy to administer) feeds plugin that acts as counterpart for restws would solve this problem for a lot of users. i tried all of these modules with "rest", "services", "clients" and so on in its name, they are way too complicated, lacking of documentation and working samples. or they are simply for writing an own module that does the "mapping part". that's not suitable for a broad bunch of people that simply want to "show off" some content from website a on websites b, c, d, e, ... in a way that they are not mentioned as duplicate content by google. do you have any tipps regarding this problem? you can reach me at frank.pfabigan @ gmail.com

SkyDoird (not verified)

Tue, 10/14/2014 - 08:00

Hi, I'm an absolute beginner in Drupal and currently using the version 7.3.1. I followed the steps given in the following link to setup the REST API. https://www.drupal.org/node/1860564 I installed and enabled the API plugin through the UI. But when API calls are according to the API documentation mentioned above, it always gives a 404 error. What can be the reason for this issue? I believe this has something to do with the permissions. Thank you in advance.

Prem Patel (not verified)

Thu, 10/16/2014 - 11:45

I was trying to use the restful API with localization module I10n but after installing I10n module I did not get the output. Can anybody help me on this topic? Is restful API support localization or not? Thanks