2018 Aug 03

A look at the RESTful API offerings in Drupal 8, with a focus on JSON API

Drupal 8 now bundles into core the RESTful project. This is an exciting time to have this available as we start to build more decoupled solutions using often the same data set. One example where this could be used is with an existing Drupal 8 site and the need for a mobile app. The app may be designed to only pull out certain types of content from the website. This would be a fairly straightforward task given the content access point provided by the web services.

Another common use case that is gaining momentum is to use Drupal as the backend to store and enter content, but develop a completely custom front end that interacts with Drupal and reads/writes data to it. There has been much talk about whether or not this is a feasible approach, but one at least worth considering.

Providing an API can be hard for many reasons, which have been discussed over on Lullabot's blog. The core offerings provide a good starting point, but other addons may be needed to help boost usability and conformity. Some other cool projects that are in the works that compliment the core RESTful web services are these:

  • JSON API - A no configuration set and forget module that helps your API conform to the JSON API specification.
  • Schemata - Provides schema definitions for Drupal entities that are available through the API
  • OpenAPI - Allows you to make your RESTful webservices discoverable using the OpenAPI standard (A.K.A. Swagger).  

Setting things up

Here we'll focus on using the JSON API module since it provides most of what you'll typically need. Also, there is talk as of this writing that the current JSON API module might be moved in to core, so it's likely to become the standard Drupal way in the near future. Lastly, the inbuilt REST setup is another hurdle to overcome given that you'd need to setup views to pull data or otherwise extend Drupal to get at the data you want. JSON API is a far easier, more standardized approach with a set it and forget it mindset. 

Drupal come with 4 web services modules in core:

WEB SERVICES

  • HAL - Serializes entities using Hypertext Application Language.
  • HTTP Basic Authentication - Provides the HTTP Basic authentication provider
  • RESTful Web Services - Exposes entities and other resources as RESTful web API
  • Serialization - Provides a service for (de)serializing data to/from formats such as JSON and XML

Start off by installing the JSON API module. You'll notice it then enables Serialization from the list above:

composer require drupal/jsonapi && drush en -y jsonapi

This is all that is needed for now to start pulling entities from Drupal.

This post will cover the GET aspect of the web services. If you require the ability to request protected content or be able to POST data to Drupal from another application, then you'd need to turn on the RESTful Web Services and HTTP Basic Authentication modules as well (depending on your needs). The JSON API module does provide the ability to read and write entities but there are some things it does not do. We encourage you to read up on the docs regarding the limitations of JSON API over at drupal.org.

Pulling data

To get an understanding of all available data, point your browser at /jsonapi

From here you'll see all the available endpoints. Every base install of Drupal should have at least basic pages, so we'll look at that. In this case, the URL will be /jsonapi/node/page

The response contains the JSON API object of up to 50 articles, including a link to page 2 of the results if there were more than 50 available.

Here you'll get a list of all the entities available for the type requested, in this case "page". Below is the first record on a test site we have setup. For those not familiar with the JSON API standard, it's become a solid reference when needing to define the data structure while developing an API. It therefore becomes easier on the development side as a recognizable way to consume data since it's fast becoming a standard. The responses from the JSON API module will always follow the structure defined in the official specification, hence the modules name. 

{
  "data": [{
        "type": "node--page",
        "id": "1fca16f0-5a6e-4e47-b7ce-21497a055228",
        "attributes": {
          "nid": 171,
          "uuid": "1fca16f0-5a6e-4e47-b7ce-21497a055228",
          "vid": 574,
          "langcode": "en",
          "revision_timestamp": 1524493691,
          "revision_log": null,
          "status": false,
          "title": "Test - Thank you page",
          "created": 1524491860,
          "changed": 1524493691,
          "promote": false,
          "sticky": false,
          "default_langcode": true,
          "revision_translation_affected": true,
          "moderation_state": "draft",
          "path": {
            "alias": "/test-thank-you-page",
            "pid": 209,
            "langcode": "en"
          },
          "content_translation_source": "und",
          "content_translation_outdated": false,
          "body": {
            "value": "<p>Test thank you page. Congratulations! You downloaded a thing.</p>\r\n\r\n<p>DOWNLOAD YOUR E-BOOK NOW [BUTTON]</p>\r\n",
            "format": "full_html",
            "processed": "<p>Test thank you page. Congratulations! You downloaded a thing.</p>\n\n<p>DOWNLOAD YOUR E-BOOK NOW [BUTTON]</p>\n",
            "summary": ""
          },
          "field_admin_title": null,
          "field_banner_text": null,
          "field_exclude_from_search_index": null,
          "field_meta": "a:0:{}"
        },
        "relationships": {
          "type": {
            "data": {
              "type": "node_type--node_type",
              "id": "021c938a-b0be-412d-b5d1-b7fa185cb5f0"
            },
            "links": {
              "self": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/relationships/type",
              "related": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/type"
            }
          },
          "revision_uid": {
            "data": {
              "type": "user--user",
              "id": "a3ac3ef7-c58d-41f2-b757-97d7f295e0d5"
            },
            "links": {
              "self": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/relationships/revision_uid",
              "related": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/revision_uid"
            }
          },
          "uid": {
            "data": {
              "type": "user--user",
              "id": "a3ac3ef7-c58d-41f2-b757-97d7f295e0d5"
            },
            "links": {
              "self": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/relationships/uid",
              "related": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/uid"
            }
          },
          "menu_link": {
            "data": null,
            "links": {
              "self": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/relationships/menu_link",
              "related": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/menu_link"
            }
          },
          "field_banner_image": {
            "data": null,
            "links": {
              "self": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/relationships/field_banner_image",
              "related": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/field_banner_image"
            }
          },
          "field_page_templates": {
            "data": null,
            "links": {
              "self": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/relationships/field_page_templates",
              "related": "http://172.16.231.130/jsonapi/node/page/1fca16f0-5a6e-4e47-b7ce-21497a055228/field_page_templates"
            }
          }
        },

 

A Parameter Reference

Set a limit with a link to the next page of available entities.

/jsonapi/node/page?page[limit]=25

Set an offset, which is also how the next link works

/jsonapi/node/page?page[limit]=25&page[offset]=25

Sort by field

/jsonapi/node/page?sort=title
/jsonapi/node/page?sort=nid

Reverse sort via a hyphen prefix

/jsonapi/node/page?sort=-title
/jsonapi/node/page?sort=-nid

Retrieving limited subsets of fields

For this one, the type and bundle must be included as well.

/jsonapi/node/page?fields[node--article]=title,created,changed,body

Conclusion

Hopefully this look at Drupal's web services provided some insights and inspiration. We just scratched the surface on this topic and it's an area that's still being expanded on. Keep looking out for improvements with the JSON API integration in Drupal core and the various other blog posts demonstrating it's use.