Last updated January 29, 2013. Created by heyrocker on April 27, 2010.
Edited by DanielFbrg, dakala, Daniel Young, jackbravo. Log in to edit this page.

We'll implement a simple service for notes that's exposed using the REST Server. Then we'll implement a simple JavaScript client for note-taking. This post is pretty much written like extended code comments and the full code and article text is available here. Note: this page contains a bug fix in the JavaScript testing logic for Create-POST and Update-Put that is not in that link. Feel free to fork this repo and flesh out the text if you want to.

Agenda

  • Getting the necessary modules
  • Implementing a note service (resource)
  • Creating a endpoint
  • Writing a simple JavaScript client

Getting the necessary modules

First off we have to download the necessary modules off Drupal.org.

Services

Download the 6.x-3.x or 7.x-3.x version from the Services project page at Drupal.org.

Chaos tool suite

The only dependency for services. Provides the framework for the endpoint definitions so that they can be exported and defined in both code and the database. Maybe further along the road it'll be used for a plugin system for the servers and authentication mechanisms.

REST Server

My server implementation of choice and what we'll be using to test our service. This is included in the Services download.

Autoload

Autoload is required by the REST Server for Drupal core 6.x. The Autoload functionality was moved into the Drupal 7 core.

The Autoload module is a utility module. It allows other modules to leverage PHP 5's class autoloading capabilities in a unified fashion.

Implementing a note service (resource)

Create a noteresource module that will contain our service implementation. The info file could look something like this:

    name = Note Resource
    description = Sample resource implementation
    package = Notes example
    dependencies[] = services
    core = 6.x
    php = 5.2

To get a really simple example I'll create an api for storing notes. In a real world scenario notes would probably be stored as nodes, but to keep things simple we'll create our own table for storing notes.

<?php
   
// noteresource.install
    /**
     * Implements hook_install().
     */
   
function noteresource_install() {
     
drupal_install_schema('noteresource');
    }
   
/**
     * Implements hook_uninstall().
     */
   
function noteresource_uninstall() {
     
drupal_uninstall_schema('noteresource');
    }
   
/**
     * Implements hook_schema().
     */
   
function noteresource_schema() {
      return array(
       
'note' => array(
         
'description' => 'Stores information about notes',
         
'fields' => array(
           
'id' => array(
             
'description' => 'The primary identifier for a note.',
             
'type' => 'serial',
             
'unsigned' => TRUE,
             
'not null' => TRUE,
            ),
           
'uid' => array(
             
'description' => t('The user that created the note.'),
             
'type' => 'int',
             
'unsigned' => TRUE,
             
'not null' => TRUE,
             
'default' => 0,
            ),
           
'created' => array(
             
'description' => t('The timestamp for when the note was created.'),
             
'type' => 'int',
             
'unsigned' => TRUE,
             
'not null' => TRUE,
             
'default' => 0,
            ),
           
'modified' => array(
             
'description' => t('The timestamp for when the note was modified.'),
             
'type' => 'int',
             
'unsigned' => TRUE,
             
'not null' => TRUE,
             
'default' => 0,
            ),
           
'subject' => array(
             
'description' => t('The subject of the note'),
             
'type' => 'varchar',
             
'length' => 255,
             
'not null' => TRUE,
            ),
           
'note' => array(
             
'description' => t('The note'),
             
'type' => 'text',
             
'size' => 'medium',
            ),
          ),
         
'primary key' => array('id'),
        ),
      );
    }
?>

The familiar stuff

Now lets implement some basic hooks and API methods. We need some permissions that'll be used to decide what our users can and cannot do:

<?php
   
// noteresource.module
    /**
     * Implements hook_perm().
     */
   
function noteresource_perm() {
      return array(
       
'note resource create',
       
'note resource view any note',
       
'note resource view own notes',
       
'note resource edit any note',
       
'note resource edit own notes',
       
'note resource delete any note',
       
'note resource delete own notes',
      );
    }
?>

Now for some Drupal API methods for the basic CRUD operations for our notes. These will be used by the functions that are used as callbacks for our resource. But it's always a good idea to supply functions like these so that other Drupal modules have a nice and clean interface to your module's data.

<?php
   
// noteresource.module
    /**
     * Gets a note object by id.
     *
     * @param int $id
     * @return object
     */
   
function noteresource_get_note($id) {
      return
db_fetch_object(db_query("SELECT * FROM {note} WHERE id=:id", array(
       
':id' => $id,
      )));
    }
   
/**
     * Writes a note to the database
     *
     * @param object $note
     * @return void
     */
   
function noteresource_write_note($note) {
     
$primary_key = !empty($note->id) ? array('id') : NULL;
     
drupal_write_record('note', $note, $primary_key);
    }
   
/**
     * Deletes a note from the database.
     *
     * @param int $id
     * @return void
     */
   
function noteresource_delete_note($id) {
     
db_query("DELETE FROM {note} WHERE id=:id", array(
       
':id' => $id,
      ));
    }
?>

Defining our resource

All resources are defined through hook_services_resources(). The way resources are declared is quite similar to how the template and menu system works, it also bears a very close resemblance to how 2.x services are defined.

Notice how we define the basic CRUD methods here: create, retrieve, update, delete (and index). Most resources implement these methods, but it is also possible to implement actions, targeted actions and relationships. Those won't be covered here but their general nature is explained in the REST Server README.

All the methods have `'file' => array('file' => 'inc', 'module' => 'noteresource'),` specified, which tells services that it can find the callback function in the file noteresource.inc, which is where we will write them all.

<?php
   
// noteresource.module
    /**
     * Implements hook_services_resources().
     */
   
function noteresource_services_resources() {
      return array(
      
'note' => array(
        
'retrieve' => array(
          
'help' => 'Retrieves a note',
          
'file' => array('file' => 'inc', 'module' => 'noteresource'),
          
'callback' => '_noteresource_retrieve',
          
'access callback' => '_noteresource_access',
          
'access arguments' => array('view'),
          
'access arguments append' => TRUE,
          
'args' => array(
             array(
              
'name' => 'id',
              
'type' => 'int',
              
'description' => 'The id of the note to get',
              
'source' => array('path' => '0'),
              
'optional' => FALSE,
             ),
           ),
         ),
        
'create' => array(
          
'help' => 'Creates a note',
          
'file' => array('file' => 'inc', 'module' => 'noteresource'),
          
'callback' => '_noteresource_create',
          
'access arguments' => array('note resource create'),
          
'access arguments append' => FALSE,
          
'args' => array(
             array(
              
'name' => 'data',
              
'type' => 'struct',
              
'description' => 'The note object',
              
'source' => 'data',
              
'optional' => FALSE,
             ),
           ),
         ),
        
'update' => array(
          
'help' => 'Updates a note',
          
'file' => array('file' => 'inc', 'module' => 'noteresource'),
          
'callback' => '_noteresource_update',
          
'access callback' => '_noteresource_access',
          
'access arguments' => array('update'),
          
'access arguments append' => TRUE,
          
'args' => array(
             array(
              
'name' => 'id',
              
'type' => 'int',
              
'description' => 'The id of the node to update',
              
'source' => array('path' => '0'),
              
'optional' => FALSE,
             ),
             array(
              
'name' => 'data',
              
'type' => 'struct',
              
'description' => 'The note data object',
              
'source' => 'data',
              
'optional' => FALSE,
             ),
           ),
         ),
        
'delete' => array(
          
'help' => 'Deletes a note',
          
'file' => array('file' => 'inc', 'module' => 'noteresource'),
          
'callback' => '_noteresource_delete',
          
'access callback' => '_noteresource_access',
          
'access arguments' => array('delete'),
          
'access arguments append' => TRUE,
          
'args' => array(
             array(
              
'name' => 'nid',
              
'type' => 'int',
              
'description' => 'The id of the note to delete',
              
'source' => array('path' => '0'),
              
'optional' => FALSE,
             ),
           ),
         ),
        
'index' => array(
          
'help' => 'Retrieves a listing of notes',
          
'file' => array('file' => 'inc', 'module' => 'noteresource'),
          
'callback' => '_noteresource_index',
          
'access callback' => 'user_access',
          
'access arguments' => array('access content'),
          
'access arguments append' => FALSE,
          
'args' => array(array(
              
'name' => 'page',
              
'type' => 'int',
              
'description' => '',
              
'source' => array(
                
'param' => 'page',
               ),
              
'optional' => TRUE,
              
'default value' => 0,
             ),
             array(
              
'name' => 'parameters',
              
'type' => 'array',
              
'description' => '',
              
'source' => 'param',
              
'optional' => TRUE,
              
'default value' => array(),
             ),
           ),
         ),
       ),
      );
    }
?>

There is another alternative when defining services (which I personally prefer) but that will probably be covered in a later article. Take a look at http://github.com/hugowetterberg/services_oop if you're curious.

Implementing the callbacks

Create the file noteresource.inc which is where we told services that it could find our callbacks.

We'll start with the create-callback. The method will receive an object describing the note that is about to be saved. The attributes we want are subject and note and we'll throw an error if those are missing. We return the id of the created note, and it's uri so that the client knows how to access it. A get-request to the uri will return the full note.

<?php
   
// noteresource.inc
    /**
     * Callback for creating note resources.
     *
     * @param object $data
     * @return object
     */
   
function _noteresource_create($data) {
      global
$user;
      unset(
$data->id);
     
$data->uid = $user->uid;
     
$data->created = time();
     
$data->modified = time();
      if (!isset(
$data->subject)) {
        return
services_error('Missing note attribute subject', 406);
      }
      if (!isset(
$data->note)) {
        return
services_error('Missing note attribute note', 406);
      }
     
noteresource_write_note($data);
      return (object)array(
       
'id' => $data->id,
       
'uri' => services_resource_uri(array('note', $data->id)),
      );
    }
?>

The update callback works more or less the same, but we don't have to check that subject and note exists, there is no harm in allowing a client to just update the subject and leave the note alone.

<?php
   
// noteresource.inc
    /**
     * Callback for updating note resources.
     *
     * @param int $id
     * @param object $data
     * @return object
     */
   
function _noteresource_update($id, $data) {
      global
$user;
     
$note = noteresource_get_note($id);
      unset(
$data->created);
     
$data->id = $id;
     
$data->uid = $note->uid;
     
$data->modified = time();
     
noteresource_write_note($data);
      return (object)array(
       
'id' => $id,
       
'uri' => services_resource_uri(array('note', $id)),
      );
    }
?>

The retrieve and delete callbacks are pretty trivial and probably don't need any further explanation.

<?php
   
// noteresource.inc
    /**
     * Callback for retrieving note resources.
     *
     * @param int $id
     * @return object
     */
   
function _noteresource_retrieve($id) {
      return
noteresource_get_note($id);
    }
   
/**
     * Callback for deleting note resources.
     *
     * @param int $id
     * @return object
     */
   
function _noteresource_delete($id) {
     
noteresource_delete_note($id);
      return (object)array(
       
'id' => $id,
      );
    }
?>

The index callback fetches a users notes and returns them all. We specified some arguments for this method that we don't use. They are mostly here to show that it would be a good idea to support paging and filtering of a index listing.

<?php
   
// noteresource.inc
    /**
     * Callback for listing notes.
     *
     * @param int $page
     * @param array $parameters
     * @return array
     */
   
function _noteresource_index($page, $parameters) {
      global
$user;
     
$notes = array();
     
$res = db_query("SELECT * FROM {note} WHERE uid=:uid ORDER BY modified DESC", array(
       
':uid' => $user->uid,
      ));
      while (
$note = db_fetch_object($res)) {
       
$notes[] = $note;
      }
      return
$notes;
    }
?>

Access checking

Last but not least, we specified an access callback for all methods. This checks so that users don't oversteps their bounds and starts looking at other people's notes without having the proper permissions. This function should be in the main .module file.

<?php
   
// noteresource.module
    /**
     * Access callback for the note resource.
     *
     * @param string $op
     *  The operation that's going to be performed.
     * @param array $args
     *  The arguments that will be passed to the callback.
     * @return bool
     *  Whether access is given or not.
     */
   
function _noteresource_access($op, $args) {
      global
$user;
     
$access = FALSE;
      switch (
$op) {
        case
'view':
         
$note = noteresource_get_note($args[0]);
         
$access = user_access('note resource view any note');
         
$access = $access || $note->uid == $user->uid && user_access('note resource view own notes');
          break;
        case
'update':
         
$note = noteresource_get_note($args[0]->id);
         
$access = user_access('note resource edit any note');
         
$access = $access || $note->uid == $user->uid && user_access('note resource edit own notes');
          break;
        case
'delete':
         
$note = noteresource_get_note($args[0]);
         
$access = user_access('note resource delete any note');
         
$access = $access || $note->uid == $user->uid && user_access('note resource delete own notes');
          break;
      }
      return
$access;
    }
?>

As you can see neither the create nor the index function is represented here. That's because they both use user_access() directly. Unlike the other methods there are no considerations like note ownership to take into account. For creation the permission 'note resource create' is checked and for the index listing only 'access content' is needed.

Creating an endpoint

The endpoint can actually be created in two ways either through the admin interface or through code. The easiest option is most often to create the endpoint through the interface, and then export it and copy paste it into your module.

Go to admin/structure/services and click "Add endpoint". Name your endpoint "notes" and call it something nice, like "Note API". Choose "REST" as your server and place the endpoint at "js-api".

Save and click the Resources tab/local task and enable all methods for the note resource. Then save your changes.

You should now have a proper working endpoint that exposes your note API. The easiest way to check that everything's working properly is to add a dummy note to your table. Then try to access it on js-api/note/[id].yaml, where [id] is the id of the note you created (probably 1).

Writing a simple JavaScript client

We'll put our javascript client in a module named noteresourcejs. The info file could look something like this:

    name = Notes Javascript
    description = Sample endpoint definition and javascript client implementation
    package = Notes example
    core = 6.x
    php = 5.2

The javascript module will do two things: implement a javascript client; and provide the notes endpoint in code.

Defining the endpoint in code

First, we need to inform the Services module that our module implements it's API. We do this by implementing hook_ctools_plugin_api() as shown in the following:

<?php
/**
* Implements hook_ctools_plugin_api().
*/
function noteresourcejs_ctools_plugin_api($owner, $api) {
  if (
$owner == 'services' && $api == 'services') {
    return array(
     
'version' => 3,
     
'file' => 'notresourcejs.services.inc', // Optional parameter to indicate the file name to load.
     
'path' => drupal_get_path('module', 'noteresourcejs') . '/includes', // If specifying the file key, path is required.
   
);
  }
}
?>

Go to admin/structure/services and select Export for your Notes API endpoint. The code shown should be copy-pasted in a hook named hook_default_services_endpoint().

<?php
   
// noteresourcejs.module
    /**
     * Implements hook_default_services_endpoint().
     */
   
function noteresourcejs_default_services_endpoint() {
     
$endpoints = array();
     
$endpoint = new stdClass;
     
$endpoint->disabled = FALSE; /* Edit this to true to make a default endpoint disabled initially */
     
$endpoint->name = 'notes_js';
     
$endpoint->title = 'Note API';
     
$endpoint->server = 'rest_server';
     
$endpoint->path = 'js-api';
     
$endpoint->authentication = array();
     
$endpoint->resources = array(
       
'note' => array(
         
'alias' => '',
         
'operations' => array(
           
'create' => array(
             
'enabled' => 1,
            ),
           
'retrieve' => array(
             
'enabled' => 1,
            ),
           
'update' => array(
             
'enabled' => 1,
            ),
           
'delete' => array(
             
'enabled' => 1,
            ),
           
'index' => array(
             
'enabled' => 1,
            ),
          ),
        ),
      );
     
$endpoints[] = $endpoint;
      return
$endpoints;
    }
?>

Notice that we don't return the endpoint as it is. But, as with views, we return an array containing the endpoint.

The client

Our client is quite trivial and will consist of one js file and one css file. I'm not going to write them both in their entirety here, but rather provide an excerpt that illustrates how you can communicate with a REST server using JavaScript. See [notes.js](services-3.x-sample/blob/master/js/notes.js) and [notes.css](services-3.x-sample/blob/master/css/notes.css) for the full versions.

    // js/notes.js (excerpt)
    // use an absolute URL path to prevent this from be local to the current page:
    var noteapi = {
      'apiPath': '/js-api/note'
    };
    // REST functions.
    noteapi.create = function(note, callback) {
      $.ajax({
         type: "POST",
         url: this.apiPath,
         data: JSON.stringify({note: note}),
         dataType: 'json',
         contentType: 'application/json',
         success: callback
       });
    };
    noteapi.retreive = function(id, callback) {
      $.ajax({
        type: "GET",
        url: this.apiPath + '/' + id,
        dataType: 'json',
        success: callback
      });
    };
    noteapi.update = function(note, callback) {
      $.ajax({
         type: "PUT",
         url: this.apiPath + '/' + note.id,
         data: JSON.stringify({note: note}),
         dataType: 'json',
         contentType: 'application/json',
         success: callback
       });
    };
    noteapi.del = function(id, callback) {
      $.ajax({
         type: "DELETE",
         url: this.apiPath + '/' + id,
         dataType: 'json',
         success: callback
       });
    };
    noteapi.index = function (callback) {
      $.getJSON(this.apiPath, callback);
    };

Notice how we don't need to do anything odd to talk with our server. Everything maps to http verbs and a url, so there is no need for special client libraries.

The js and css is added in hook_init(), and will therefore be loaded on all pages in our Drupal install.

<?php
   
// noteresourcejs.module
    /**
     * Implements hook_init().
     */
   
function noteresourcejs_init() {
     
drupal_add_css(drupal_get_path('module', 'noteresourcejs') . '/css/notes.css');
     
drupal_add_js(drupal_get_path('module', 'noteresourcejs') . '/js/notes.js');
    }
?>

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

One gotcha that took me a while to figure out is that you must set the access callback value to a defined function. Unlike with hook_menu(), using a boolean value will not bypass the access check and instead results in a PHP error, as services passes the value straight through to call_user_func_array().

services.runtime.inc - line 135

<?php
 
// Call default or custom access callback
 
if (call_user_func_array($controller['access callback'], $access_arguments) != TRUE) {
   
// return 401 error
 
}
?>

In addition, resource definitions are cached in the database, so if you're making modifications be sure to clear the cache or remove entries matching services:%:resources so that the new values in code are used.

Does anyone have info on PUT and DELETE support in browsers? I can't find a place where the code is handling the ajax type and I thought the last two weren't widely supported.

Joe Tippetts

Only GET and POST are supported for HTML forms, so other requests will always require a javascript handler.
The jQuery documentation for .ajax() simply notes "Other HTTP request methods, such as PUT and DELETE, can also be used here, but they are not supported by all browsers." The standard for XMLHttpRequest specifies that conforming browsers must support GET, POST, HEAD, PUT, DELETE, and OPTIONS.

This Stack Overflow question has some information on browser support:
One mentions this article that tested Firefox 3, Opera 9, and IE 7 for support of alternate request types, and all should support PUT or DELETE requests. FF may have had support as far back as a 1.x version.
Another answer notes that Safari added support in either version 3 or 4.

I was unable to find a definitive answer as to whether IE 6 supports PUT and DELETE requests through the XMLHttp ActiveX Object

I followed the instructions based on above, and the same tutorial in here: https://github.com/hugowetterberg/services-3.x-sample/blob/master/creati...
I already downloaded all the required module, which is:
- Services 6.x-3.0-rc1
- Input stream 6.x-1.0
- Autoload 6.x-2.1
- Rest Server 6.x-2.0-beta3
- Chaos tool suite 6.x-1.x-dev
I already download, the of noteresource module from here: https://github.com/hugowetterberg/services-3.x-sample
I also already implemented the patch of issue in Services module in here: http://drupal.org/node/1153968

But still, when I try post something from that javascript form, it says from the Firebug:
POST http://localhost:8888/drupal62/js-api/note 500 Internal Server Error

Am I missing something here?
or is it because I used http://localhost:8888/drupal62? (which is I don't think so)
Should I put something in here: http://localhost:8888/drupal62/admin/build/services/list/notes/resources
Should I allow permissions for noteresouce module for all users (tick all checkboxes for anonymous and authenticated)?

Thanks in advance.

ei, but does it work for you when consuming the service, just consulting a resource? i don't know how to make it run! i'm lost... i have a node (id = 1) and i try to get into the node by rest, using firefox... so i do try to get into http://localhost/drupal6/services/rest/node/1 but i what i get is a 404 error. I'm a real noob, sorry!

Paloma Jiménez
Planet Media

Hi paloma.jimenez
Sorry just replied, yes, it is kinda works for me for consuming the services, I eventually found out how to do that, but not all though.
First of all, assuming that you already added endpoint, you need to enable some of the resources by clicking link Edit Resources, then you will see list of Resources there.

I only understand some part of the resources, for example: tick "node.retrieve"

Then in your URL, you type this: http://something.com/[your endpoint]/node/retrieve
after that you will see list of node display there as XML.

If you need to display only certain node, then do this: http://something.com/[your endpoint]/node/[nid]

Hopefully that giving you some light.

Cheers

I tried everything, but I can't get it working for Drupal 7.x

All these give me error: 404 Not Found in stead of a white screen or another expected result.

http://localhost/drupal/js-api/
http://localhost/drupal/js-api/note/
http://localhost/drupal/js-api/notes/
http://localhost/drupal/notes/
http://localhost/drupal/js-api/note/1.yaml
http://localhost/drupal/js-api/notes/1.yaml
http://localhost/drupal/notes/1.yaml

Where can I find help on this or find another example?

***

Sorry, it was working from the start!
But I did not have "Clean URL" activated on the test site.

http://localhost/drupal/?q=js-api gives the white screen and not the error 404.

Oh yeah, I removed the obsolete function "db_fetch_object"

//noteresource.inc
function _noteresource_index($page, $parameters) {
  global $user;
  $notes = array();
  $res = db_query("SELECT * FROM {note} WHERE uid=:uid ORDER BY modified DESC", array(':uid' => $user->uid));
  /*while ($note = db_fetch_object($res)) {
    $notes[] = $note;
  }*/
  foreach ($res as $note) {
     $notes[] = $note;
  }
  return $notes;
}
//noteresource.module
function noteresource_get_note($id) {
  //return db_fetch_object(db_query("SELECT * FROM {note} WHERE id=:id", array(':id' => $id)));
  $result = db_query("SELECT * FROM {note} WHERE id=:id", array(':id' => $id));
  return $result->fetchObject();
}

Maybe, this can be of help for other dummies...

Dear Druplers,

Please refer to my comments in http://drupal.org/node/1246470 , if you want to get the above code working in Drupal 7.7x

Thx.

"Lets Make Things Better"

Thank you. your example helped me a bunch!

When setting up my own first 3.x service, I kept getting errors like "cannot find controller". I found myself asking the question, "how does a certain request map into the service definition I set? It took me a bit of digging into the code to get into the right frame of mind to understand how Services is deciding where and how to dispatch requests. A good API documentation for the services_resources callback really seems to be in order. While I don't feel qualified for that yet, here is some of what I've learned.

The first thing to remember is that the REST module is using the REST assumption that certain HTTP methods correspond to certain things. As you can see in the REST server code there is a simple algorithm that determines where to dispatch a request:

  • First, the code counts the number of "path" elements, i.e. the part after the resource name. This is called the "path count.
  • If the path count is php-false (e.g. zero) and the http method is not POST, the request always goes to the 'index' action you have set in your resource definition.
  • If the path count is exactly one, or if the method is POST and the path count is zero, the action is selected based on the http request method:
    <?php
        $action_mapping
    = array(
         
    'GET' => 'retrieve',
         
    'POST' => 'create',
         
    'PUT' => 'update',
         
    'DELETE' => 'delete',
        );
    ?>

For other numbers of arguments, I haven't tried the code, but you should look at it yourself as it has some specific assumptions. For example for GET requests with more than 1 path-part, the code will look for a 'relationships' part of the resource definition. This probably corresponds to some REST practice that I haven't figured out yet. For my purposes, I decided to make my multi-argument GET methods use the "param" argument type.

Man, I wish I'd seen this comment before tracing through the code myself and coming to the same conclusion. This documentation is rather lacking if you want to go at all beyond the basic CRUD functions shown.

Relationships appear to be strictly for returning additional information about a specific object. For example, you can do something like node/[nid]/files to get the files associated with a node.

The missing piece for me is the array key called 'actions' -- this is where you can add things besides Create, Index, Retrieve, Delete. See services/resources/system_resource.inc for an example. Note that you can only POST to actions.

I get why it's set up this way, but the documentation could be a lot better and I really think there should be a way to offer arbitrary GET handlers. For example, there's no good reason from a RESTful standpoint for system.variable_get to require a POST.

I wrote up and posted working examples of the above, with the following modifications:

  • I removed any database logic so the example code is demonstrating how to implement REST API logic only,
  • I added logic for Relationships and Targeted Actions to demonstrate implementing those.

Services 3 examples
Cheers!

The example create method contains some error handling that could use improvement:

<?php
if (!isset($data->subject)) {
  return
services_error('Missing note attribute subject', 406);
}
?>

This will set the HTTP response to 406, but unfortunately the $message is dropped and the response body is blank. Not good.

Best practices dictate you should return some useful information in the message body about the error. You can accomplish this by setting the third param ($data) of the services_error method. Here is a snippet of code I use:

<?php
function _process_services_error($message, $code) {
 
$data = array('error' => array('code' => $code, 'message' => $message));
 
services_error($message, $code, $data);
}
?>

replace the call to services_error in the example to _process_services_error and your service call will contain the more useful response body (Content-Type: application/json):

{"error":{"code":406,"message":"Missing note attribute subject"}}

Great post!!

I tried using XML-RPC, because I need android functionality.
I'm getting always this error: "XML-RPC server accepts POST requests only."
I'm pretty sure I'm sending POST request to the server:
I tried with a js:

noteapi.index = function (callback) {
  jQuery.ajax({
    type: "POST",
    url: this.apiPath + '/index',
    success: callback,
  });
};

Also with firefox poster, same result.
Also tried (http://drupal.org/node/1056524) activating language by url to avoid drupal redirection. Nothing happens but always the same message..

Is there any drupal redirection I'm missing? If yes, how could discover/avoid the redirection?
In firebug console only appears "POST http://localhost/drupaltest/public_html/en/js-api/note/index"

Please, need help!!

The example worked for me perfectly. But then again, I'm running Drupal 7 and wrote out my example to take advantage of a lot of the simplified changes to the architecture. I tested mine first with json then urlencoded response formats running under FireFox POSTER and then used "drupal_http_request" function client side so I could work faster and not have to bother with javascript as I tend to like things quick, direct and dirt simple. I noticed that although it was easy to do the CRUD, Actions, Targeted Actions and Relationsip style resources for the REST server and the dev version of the server shows you categories nicely listed under the appropriate resource, the definition module doesn't yet support the dev version but only the last official release.

Cheers

Am I missing something or does schema unininstall do the same thing as schema install?

// noteresource.install
    /**
     * Implements hook_install().
     */
    function noteresource_install() {
      drupal_install_schema('noteresource');
    }
    /**
     * Implements hook_uninstall().
     */
    function noteresource_uninstall() {
      drupal_install_schema('noteresource');
    }

Just askin'

Never Know When to Quit.

A schema describes the data sctructure in a database, or in the case of this example the structure of a table. Whether you want to install or uninstall, you need to have the structure defined. When doing an "uninstall" if the schema itself is defined then removing the items from the table can be checked against the "schema", just as it is when its installed.

When using Drupal 7 its good to see exactly what is going on and why. For example when examining the data description for the "create" method. The "name" of the item is "data" and the type is shown as "struct' with the source being "data". That means the resource expects the passing of arguments to come from the body content inside a "stucture" called "data". Since one of my request parsing formats chosen was "application/x-www-form-urlencoded" that meant the body of the data would be expected to contain a structure named "data' which would have keys named "subject", "note" and so on. That meant I could read the data passed as an array or cast it to an object ie. $data['data']['subject'] or (object)$data['data'] so I could assign $data=(object)$data['data'] which would now make $data->note now give the correct value as per the example. If you're using drupal 7, don't just take my word for it. Check it out for yourself on the actual data. A lot of the information that RESTful methods expect to come passed in arguments in the body of the method call is actually originating in headers. In fact the Firefox plug-in/extension "POSTER" is actually passing its data as a Cookie in a Cookie Header. Once again, don't take my word for it, check out the actual header data and see for yourself when using POSTER.

Cheers

There is a small improvement needed in your code. If for example the user requests a resource by putting the accept type in the GET url:

...{etc}.../resource.json

Your service will return data whose URI doesn't include this extension (eg: .../resource/1 instead of /resource/1.json). It would be better to include this extension.

The problem code is here:

<?php
'uri' => services_resource_uri(array('note', $data->id)),
?>

I have written a small function to fix this, and also handle if you want to pass back URL params (for example to allow the user to request pages of data in the list function):

<?php
function _get_resource_uri($path, $params = null) {
 
$uri = services_resource_uri($path);
 
$request = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
 
$dot_pos = strrpos($request,'.');
  if (!(
$dot_pos === FALSE)) $uri .= substr($request,$dot_pos);
  if (isset(
$params) && count($params) > 0) $uri.= '?' . http_build_query($params, '', '&');
  return
$uri;
}
?>

A more robust version would check the actual request extension against valid service accept types (json,xml,etc). This is an exercise left to the reader.

In the current implementation of the rest_server, this will make your example inoperable, you cannot have the endpoint name be the same as the resource name you intend to call with it.

Allright now how do we set the response type to json? I'm retrieving the fid of a node (say 450) from the database and I want to return the response as:
{fid:450} for example.

Right now what I'm doing is:

return $fid;

which returns a string of "450"

so how do I use JSONparse for the response?

drupal_json_encode

In 6 this is simpy known as drupal_json.

With this declaration there is an error:
'file' => array('file' => 'inc', 'module' => 'noteresource'),
it must be:
'file' => array('type' => 'inc', 'module' => 'noteresource'),

This page does not fit with the definition of hook_services_resources() at http://drupalcode.org/project/services.git/blob/refs/heads/7.x-3.x:/docs...

--
Håvard Pedersen
Web developer at Norwegian Centre for Integrated Care and Telemedicine, working with eLearning for the health sector.