Restful web services in Drupal 7

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>

Comments

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. =)

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.

There is no JSONP as of now. But please, let's use the modules issue queue for any specifc questions.

Added a request in the module issues queue

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!

I'm afraid this will not be RESTfull.

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

Yes What about Image Fields, media fields ?

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

I'm wondering if this module is fully functional with the drupal search?
Forex Brokers

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.

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.

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.

please guide me to create custom webservice with restws.

thanks.

Add new comment

Filtered HTML

  • To post pieces of code, surround them with <code>...</code> tags. For PHP code, you can use <?php ... ?>, which will also colour it based on syntax.
  • Allowed HTML tags: <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.