Skip to main content

Requests and Responses the Drupal way

Building your own REST API:

Symfony's Response objects are fully supported, but are insufficient to fully support the rich Drupal ecosystem: we need more structured metadata than the very simple Symfony Response objects can provide.
Unfortunately, Symfony Response objects do not have an interface: every specialized Response "type" needs to extend from Symfony's Response base class.

Drupal core defines two response interfaces that any response can implement to indicate it supports these particular Drupal capabilities.

CacheableResponseInterface  An interface for responses that can expose cacheability metadata. 
(Cache contexts, tags and max-age.)
Note: can easily be implemented by using the corresponding CacheableResponseTrait.
See CacheableResponseInterface for more about this!

AttachmentsInterface  An interface for responses that can expose #attached metadata. 
(Asset libraries, <head> elements, placeholders …)
Note: can easily be implemented by using the corresponding AttachmentsTrait.

CacheableResponse  A response that contains and can expose cacheability metadata. 
Supports Drupal's caching concepts: cache tags for invalidation and cache contexts for variations.
This is simply class CacheableResponse extends Response implements CacheableResponseInterface {}.

HtmlResponse  This is what a controller returning a render array will result in after going through the Render API and its render pipeline.
This is simply class HtmlResponse extends Response implements CacheableResponseInterface, AttachmentsInterface {}.

CacheableJsonResponse  A JsonResponse that contains and can expose cacheability metadata.
This is simply class CacheableJsonResponse extends JsonResponse implements CacheableResponseInterface {} — i.e. it extends Symfony's JsonResponse.

CacheableRedirectResponse  A RedirectResponse that contains and can expose cacheability metadata.
This is simply class CacheableRedirectResponse extends RedirectResponse implements CacheableResponseInterface {} — i.e. it extends Symfony's RedirectResponse.

LocalRedirectResponse  A redirect response which cannot redirect to an external URL. 
(Extends CacheableRedirectResponse.)

TrustedRedirectResponse  A redirect response which should only redirect to a trusted (potentially external) URL. 
(Also extends CacheableRedirectResponse.)

 

Core Module: RESTful Web Services

This module when activated support the following API:

  1. Users API
    1. Login
      1. POST http://example.com/user/login?_format=json
      2. Content-type: application/json
      3. withCredentials: true (only for cross domain authentication)
        {
          "name": "admin",
          "pass": "password"
        }
      4. return: 200 OK
    2. Logout
      1. POST http://example.com/user/logout?_format=json&token=logout_token
      2. Content-type: application/json
      3. withCredentials: true (only for cross domain authentication)
      4. return: 204 - OK
    3. Retrieve
      1. GET http://example.com/user/1?_format=json
      2. None
      3. return: 200 - OK
    4. Register
      1. POST: https://example.com/user/register?_format=json
      2. Content-type: application/json
        {
         "name": { "value": "fooBar" },
         "mail": { "value": "foo@bar.com" },
         "pass": { "value": "secretSauce" }
        }
      3. return: 200 OK
  2. Node API
    1. Create
      1. POST: http://example.com/entity/node
      2. Content-type: application/json
        {
         "type":[{"target_id":"article"}],
         "title":[{"value":"Hello World"}],
         "body":[{"value":"How are you?"}]
        }
      3. return: 201 - Created
      4. For setting the value of an entity reference field referencing another entity type, all you need is its uuid:
        "_embedded": {
         "https://example.com/rest/relation/node/article/my_entity_reference_field": [
           { 
             "uuid":[{"value":"yourUUID-xxx-xxxx-xxxx-xxxxxxxxx"}]
           }
         ]
        }
    2. Retrieve
      1. GET: http://example.com/node/123?_format=json
      2. Content-type: *
      3. Accept: application/json
      4. return: 200 - OK
    3. Update
      1. PATCH: http://example.com/node/123
      2. Content-type: application/json
        {
         "nid":[{"value":"123"}],
         "type":[{"target_id":"article"}],
         "title":[{"value":"Goodbye World"}]
        }
      3. return: 204 - No Content
    4. Delete
      1. DELETE: http://example.com/node/123
      2. Content-type: *
        {"type":[{"target_id":"article"}]}
      3. return: 204 - No Content

         

Results are Automatically Cached from GET Requests
While developing, it's important to understand that when you make a GET request to D8 Rest, Drupal will cache the result so subsequent requests receive a speedy response. You can either clear all of Drupal's caches to get the new results, or append a timestamp to the URL query string:

node/123?_format=json&time=123456789

Or if you've created a custom resource, use addCacheableDependency() on the ResourceResponse:

$response = new ResourceResponse(array('hello' => 'world'));
$response->addCacheableDependency($account);
return $response;

RESTful Example using jQuery and core REST module

CREATE Item

var package = {}
package.title = [{'value':'t1'}]
package.body = [{'value':'b1'}]
package._links = {"type":{"href":"http://local.drupal8.org/rest/type/node/page"}}
$.ajax({
  url: "http://example.com/entity/node",
  method: "POST",
  data: JSON.stringify(package),
  headers: {
    "Accept": "application/json",
    "Content-Type": "application/hal+json"
  },
  success: function(data, status, xhr) {
    debugger
  }
})

GET Item

$.ajax({
  url: "http://example.com/node/3?_format=hal_json",
  method: "GET",
  headers: {
    "Content-Type": "application/hal+json"
  },
  success: function(data, status, xhr) {
    debugger
  }
})

GET an Item and then UPDATE Item

$.ajax({
  url: "http://example.com/node/3?_format=hal_json",
  method: "GET",
  headers: {
    "Content-Type": "application/hal+json"
  },
  success: function(data, status, xhr) {
    var package = {}
    package.title = data.title
    package.body = data.body
    package.title[0].value = 'yar'
    package._links = {"type":{"href":"http://example.com/rest/type/node/page"}}
    debugger

    $.ajax({
      url: "http://example.com/node/3",
      method: "PATCH",
      data: JSON.stringify(package),
      headers: {
        "X-CSRF-Token": "niCxgd5ZZG25YepbYtckCy7Q2_GL2SvMUY5PINxRAHw",
        "Accept": "application/json",
        "Content-Type": "application/hal+json"
      },
      success: function(data, status, xhr) {
        debugger
      }
    })
  }
})

Drupal protects its REST resources from CSRF attacks by requiring a X-CSRF-Token request header to be sent when using a non-safe method. So, when performing non-read-only requests, that token is required. 
Such a token can be retrieved at /session/token.

GET an Item and then UPDATE Item with CSRF token.

$.ajax({
  url: 'http://example.com/session/token',
  method: 'GET',
  success: function(token) {
    var csrfToken = token;

    $.ajax({
      url: "http://example.com/node/3?_format=hal_json",
      method: "GET",
      headers: {
        "Content-Type": "application/hal+json"
      },
      success: function(data, status, xhr) {
        var package = {};
        package.title = data.title;
        package.body = data.body;
        package.title[0].value = 'yar';
        package._links = {"type":{"href":"http://example.com/rest/type/node/page"}};
        debugger

        $.ajax({
          url: "http://example.com/node/3",
          method: "PATCH",
          data: JSON.stringify(package),
          headers: {
            "X-CSRF-Token": csrfToken,
            "Accept": "application/json",
            "Content-Type": "application/hal+json"
          },
          success: function(data, status, xhr) {
            debugger;
          }
        });
      }
    });
  }
});

DELETE Item

$.ajax({
  url: "http://example.com/node/3",
  method: "DELETE",
  headers: {
    "Accept": "application/json",
    "Content-Type": "application/hal+json"
  },
  success: function(data, status, xhr) {
    debugger
  }
})