GithubHelp home page GithubHelp logo

restful's Introduction

Build Status

RESTful best practices for Drupal

This module follows this post to achieve a practical RESTful for Drupal. The aim of the module, is to allow exposing an API, without Drupal's data structure leaking to it.

Concept

The following also describes the difference between other modules such as RestWs and Services Entity.

  • Restful module requires explicitly declaring the exposed API. When enabling the module nothing will happen until the implementing developer will declare it
  • Instead of exposing resources by entity type (e.g. node, taxonomy term), Restful cares about bundles. So for example you may expose the Article content type, but not the Page content type
  • The exposed properties need to be explicitly declared. This allows a clean output without Drupal's internal implementation leaking out. This means the consuming client doesn't need to know if an entity is a node or a term, nor will they be presented with the field_ prefix
  • One of the core features is versioning. While it's debatable if this feature is indeed a pure REST, we believe it's a best practice one
  • Only JSON format is supported
  • Audience is developers and not site builders
  • Provide a key tool for a headless Drupal. See the AngularJs form example module.

Module dependencies

API via Drupal

Assuming you have enabled the RESTful example module

Getting handlers

// Get handler v1.0
$handler = restful_get_restful_handler('articles');

// Get handler v1.1
$handler = restful_get_restful_handler('articles', 1, 1);

Create and update an entity

$handler = restful_get_restful_handler('articles');
// POST method, to create.
$result = $handler->post('', array('label' => 'example title'));
$id = $result['id'];

// PATCH method to update only the title.
$request['label'] = 'new title';
$handler->patch($id, $request);

View an entity

// Handler v1.0
$handler = restful_get_restful_handler('articles');
// GET method.
$result = $handler->get(1);

// Output:
array(
  'id' => 1,
  'label' => 'example title',
  'self' => 'https://example.com/node/1',
);

// Handler v1.1 extends v1.0, and removes the "self" property from the
// exposed properties.
$handler = restful_get_restful_handler('articles', 1, 1);
$result = $handler->get(1);

// Output:
array(
  'id' => 1,
  'label' => 'example title',
);

Filtering fields

Using the ?fields query string, you can decalre which fields should be returned.

$handler = restful_get_restful_handler('articles');

// Define the fields.
$request['fields'] = 'id,label';
$result = $handler->get(2, $request);

// Output:
array(
  'id' => 2,
  'label' => 'another title',
);

List entities

$handler = restful_get_restful_handler('articles');
$result = $handler->get();

// Output:
array(
  'data' => array(
    array(
      'id' => 1,
      'label' => 'example title',
      'self' => 'https://example.com/node/1',
    );
    array(
      'id' => 2,
      'label' => 'another title',
      'self' => 'https://example.com/node/2',
    );
  ),
);

Sort

You can sort the list of entities by multiple properties. Prefixing the property with a dash (-) will sort is in a descending order. If no sorting is specified the default sorting is by the entity ID.

$handler = restful_get_restful_handler('articles');

// Define the sorting by ID (descending) and label (ascending).
$request['sort'] = '-id,label';
$result = $handler->get('', $request);

// Output:
array(
  'data' => array(
    array(
      'id' => 2,
      'label' => 'another title',
      'self' => 'https://example.com/node/2',
    );
    array(
      'id' => 1,
      'label' => 'example title',
      'self' => 'https://example.com/node/1',
    );
  ),
);

Filter

RESTful allows filtering of a list.

$handler = restful_get_restful_handler('articles');
// Single value property.
$request['filter'] = array('label' => 'abc');
$result = $handler->get('', $request);

Autocomplete

By passing the autocomplete query string in the request, it is possible to change the normal listing behavior into autocomplete.

The following is the API equivilent of https://example.com?autocomplete[string]=foo&autocomplete[operator]=STARTS_WITH

$handler = restful_get_restful_handler('articles');

$request = array(
  'autocomplete' => array(
    'string' => 'foo',
    // Optional, defaults to "CONTAINS".
    'operator' => 'STARTS_WITH',
  ),
);

$handler->get('', $request);

API via URL

View an Article

# Handler v1.0
curl https://example.com/api/v1/articles/1

# Handler v1.1
curl https://example.com/api/v1/articles/1 \
  -H "X-Restful-Minor-Version: 1"

View multiple Articles at once

# Handler v1.1
curl https://example.com/api/v1/articles/1,2 \
  -H "X-Restful-Minor-Version: 1"

Authentication providers

Restful comes with cookie, base_auth (user name and password in the HTTP header) authentications providers, as well as a "RESTful token auth" module that has a token authentication provider.

Note: if you use cookie-based authentication then you also need to set the HTTP X-CSRF-Token header on all writing requests (POST, PUT and DELETE). You can retrieve the token from /api/session/token with a standard HTTP GET request.

See this AngularJs example that shows a login from a fully decoupled web app to a Drupal backend.

# (Change username and password)
curl -u "username:password" https://example.com/api/login

# Response has access token.
{"access_token":"YOUR_TOKEN"}

# Call a "protected" with token resource (Articles resource version 1.3 in "Restful example")
curl https://example.com/api/v1/articles/1?access_token=YOUR_TOKEN \
  -H "X-Restful-Minor-Version: 3"

Error handling

While a PHP Exception is thrown when using the API via Drupal, this is not the case when consuming the API externally. Instead of the exception a valid JSON with code, message and description would be returned.

The RESTful module adheres to the Problem Details for HTTP APIs draft to improve DX when dealing with HTTP API errors. Download and enable the Advanced Help module for more information about the errors.

For example, trying to sort a list by an invalid key

curl https://example.com/api/v1/articles?sort=wrong_key

Will result with an HTTP code 400, and the following JSON:

{
  'type' => 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1',
  'title' => 'The sort wrong_key is not allowed for this path.',
  'status' => 400,
  'detail' => 'Bad Request.',
}

Sub-requests

It is possible to create multiple referencing entities in a single request. A typical example would be a node referencing a new taxonomy term. For example if there was a taxonomy reference or entity reference field called field_tags on the Article bundle (node) with an articles and a Tags bundle (taxonomy term) with a tags resource, we would define the relation via the RestfulEntityBase::publicFieldsInfo()

public function publicFieldsInfo() {
  $public_fields = parent::publicFieldsInfo();
  // ...
  $public_fields['tags'] = array(
    'property' => 'field_tags',
    'resource' => array(
      'tags' => 'tags',
    ),
  );
}

And create both entities with a single request:

$handler = restful_get_restful_handler('articles');
$request = array(
  'label' => 'parent',
  'body' => 'Drupal',
  'tags' => array(
    array(
      // Create a new term.
      'label' => 'child1',
    ),
    array(
      // PATCH an existing term.
      'label' => 'new title by PATCH',
    ),
    array(
      '__application' => array(
        'method' => \RestfulInterface::PUT,
      ),
      // PUT an existing term.
      'label' => 'new title by PUT',
    ),
  ),
);

$handler->post('', $request);

Cache layer

The RESTful module is compatible and leverages the popular Entity Cache module and adds a new cache layer on its own for the rendered entity. Two requests made by the same user requesting the same fields on the same entity will benefit from the render cache layer. This means that no entity will need to be loaded if it was rendered in the past under the same conditions.

Developers have absolute control where the cache is stored and the expiration for every resource, meaning that very volatile resources can skip cache entirely while other resources can have its cache in MemCached or the database. To configure this developers just have to specify the following keys in their restful plugin definition.

Rate Limit

RESTful provides rate limit functionality out of the box. A rate limit is a way to protect your API service from flooding, basically consisting on checking is the number of times an event has happened in a given period is greater that the maximum allowed.

Rate Limit events

You can define your own rate limit events for your resources and define the limit an period for those, for that you only need to create a new rate_limit CTools plugin and implement the isRequestedEvent method. Every request the isRequestedEvent will be evaluated and if it returns true that request will increase the number of hits -for that particular user- for that event. If the number of hits is bigger than the allowed limit an exception will be raised.

Two events are provided out of the box: the request event -that is always true for every request- and the global event -that is always true and is not contained for a given resource, all resources will increment the hit counter-.

This way, for instance, you could define different limit for read operations than for write operations by checking the HTTP method in isRequestedEvent.

Configuring your Rate Limits

You can configure the declared Rate Limit events in every resource by providing a configuration array. The following is taken from the example resource articles 1.4 (articles__1_4.inc):

'rate_limit' => array(
    // The 'request' event is the basic event. You can declare your own events.
    'request' => array(
      'event' => 'request',
      // Rate limit is cleared every day.
      'period' => new \DateInterval('P1D'),
      'limits' => array(
        'authenticated user' => 3,
        'anonymous user' => 2,
        'administrator' => \RestfulRateLimitManager::UNLIMITED_RATE_LIMIT,
      ),
    ),
  ),
…

As you can see in the example you can set the rate limit differently depending on the role of the visiting user.

Since the global event is not tied to any resource the limit and period is specified by setting the following variables:

  • restful_global_rate_limit: The number of allowed hits. This is global for all roles.
  • restful_global_rate_period: The period string compatible with \DateInterval.

Modules integration

Credits

restful's People

Contributors

amitaibu avatar

Watchers

 avatar James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.