Decoupling Drupal 8 with JSON API

In a previous installment of Experience Express, we explored how effective core REST out of the box can be for the purposes of consuming content retrieved from Drupal and manipulating said data as well. Furthermore, with the help of Views, any content listing can be easily converted into an API resource. As these features indicate, Drupal 8 is a powerful web services provider that can expose content to any consumer application on any channel.

Nonetheless, sometimes our requirements far exceed the functionality available to us. As we saw in the previous post, core REST only allows for individual entities to be retrieved, and Views REST exports only permit the issuance of GET requests rather than unsafe methods as well. But application developers often need greater flexibility and control, such as the ability to fetch collections, sort and paginate them, and access related entities that are referenced.

In this column, we'll inspect JSON API, part of the surrounding contributed web services ecosystem that Drupal 8 relies on to provide even more extensive features relevant to application developers that include relationships and complex operations such as sorting and pagination.

The JSON API specification

JSON API, an emerging specification for REST APIs in JSON that dubs itself an “anti-bikeshedding tool,” has recently gained traction because of its adoption by developers in the Ember and Ruby on Rails communities and its robust approaches to resource relationships and common query operations such as pagination and sorting. Drupal's implementation of the specification, the JSON API module, is nearing inclusion in Drupal 8 core as an experimental module.

The JSON API specification describes itself as:

"[A] specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests.

JSON API is designed to minimize both the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability."

Because of Drupal's approach to entity relationships through entity references, Drupal's data structures (entity types, bundles, and fields) are remarkably optimal for consumption and manipulation with the JSON API specification.1

The JSON API module

The stated goal of the JSON API module is to require as little configuration as possible.2 Therefore, when you install and enable the JSON API module, a REST API is immediately available for every type within your Drupal implementation. The module achieves this by traversing entity types and bundles such that it can generate URLs at which it can access and manipulate entities by employing safe and unsafe HTTP methods.

This vision of "no configuration" and production-readiness out of the box does come with several disadvantages, among them that the JSON API module is extremely opinionated about the paths at which your resources are available, the methods against which requests can be issued, and the permissioning by which entities can be accessed and manipulated. Permissions for the JSON API module fall back to the core roles and permissions system rather than a separate configuration page (unlike core REST).1

Refresher: Setting up Drupal as a web service provider

Once you have decided to use the JSON API module, you can enable and install it without any additional configuration. You can either use the core REST local environment we have set up, or you can set up a new local site that is dedicated to your JSON API testing. To do the latter, here is a quick command-line refresher on setting up Drupal for web services testing, with the added benefit of using Composer to provide our dependencies:

$ composer create-project drupal/drupal jsonapi-test
$ cd jsonapi-test

From here, you can import your local Drupal site into a program that can manage local development environments, such as Acquia Dev Desktop. At that point, you can install Drupal using the web interface or by using drush si. Then, we'll want to generate content that we can test.

$ drush dl devel && drush en -y devel
$ drush en -y devel_generate
$ drush genc 20 && drush genu 20

Because the JSON API module depends on Serialization, we'll want to enable that module, but we do not need to enable HAL, Basic Authentication, or RESTful Web Services.

$ drush en -y serialization
$ drush dl jsonapi && drush en -y jsonapi

Recall from a previous installment of Experience Express that because we have not yet covered authentication (coming soon), we also need to set permissions to allow anonymous users to issue requests using unsafe HTTP methods against Drupal — solely on a local environment for testing purposes.

Retrieving resources with JSON API

While the JSON API specification recommends that all requests include an Accept header with the correct MIME type for JSON API, the JSON API module accepts requests without any request headers present.3 Since we have not yet covered authentication, we will continue to allow anonymous users to both read and write entities.

Accept: application/vnd.api+json

Retrieving single resources

To retrieve a single resource, you need the identifier for the resource, keeping in mind that the required identifier is not a nid as in core REST, but rather a UUID. As such, you can retrieve a single article by simply issuing a GET request against the following URL:

/jsonapi/node/article/{{node_uuid}}

Ensure that the bundle referred to in the request reflects the entity requested. The ensuing response will include a response code of 200 OK and its body will contain the JSON API object of a single article node, including attributes (fields), available relationships, and links.3

Note: You can retrieve a particular node's UUID with the help of the Devel module, which we installed in the previous section. Navigate to the node in question, switch from the View to the Devel tab, and you can find the UUID under the Variable section, as in the screenshot below. Another option is to enable core REST modules and issue a GET request against the entity, whose response will include the UUID.

image7_0.png

Retrieving resource collections

One of the key motivations for using JSON API instead of core REST is the possibility of retrieving multiple resources using JSON API collections. In Drupal 8 core, whereas individual entities can be retrieved using core REST, Views REST exports are the only available mechanism by which entity collections can be retrieved. In JSON API, simply issue a GET request against the following URL to retrieve a collection of articles:

/jsonapi/node/article

The resulting response will include a 200 OK response code as well as a maximum of 50 articles within the JSON API data object, along with a link to the next page of available articles in the collection.

Thanks to the thoroughness of the JSON API specification, you can use certain query parameters to operate on the collections retrieved through the API. For instance, the following request includes a limit of 25 articles and a link to the next page of available articles.

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

To retrieve the second page of 25 articles, you can use page[offset] to ensure that only articles 26-50 are represented in the response from the server.

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

You can also perform sorts on the fly within the requests you issue by using the sort query parameter, which allows you to sort results by attributes. In the following examples, the title and nid are used in separate requests.

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

To reverse the order, prefix the value with a hyphen to indicate that an ascending order should be descending instead.

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

Retrieving limited subsets of fields

For APIs that serve a diverse range of clients, including consumers that need small payload sizes for performance reasons, the full response from the JSON API can frequently be overkill for the purposes of consumer applications. To retrieve a limited subset of fields, just as we did with sorts and pagination on collections, we'll need to use query parameters to enumerate which fields we wish to handle.

For instance, to acquire only the title, created and changed timestamps, and body of the article, you can provide a fields query parameter. Note that in conjunction with the parameter, the type and bundle must be included as well.

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

Creating resources with JSON API

The JSON API specification allows for individual resources to be created through an API, but not multiple at the same time. This is also true of updating and deleting resources through JSON API.4 Usually, with unsafe methods such as POST, PATCH, and DELETE, we will need to include authentication on production, but we'll be covering that later.

The following request headers are obligatory on all POST requests in order to generate a standard response:

Accept: application/vnd.api+json
Content-Type: application/vnd.api+json

To issue a POST request against Drupal that creates an article, use the same URL that we used to retrieve collections of articles:

/jsonapi/node/article

And include a request payload that contains the required fields:

{
  "data": {
    "type": "node--article",
    "attributes": {
      "title": "My snazzy new article",
      "body": {
        "value": "Hello world! Lorem ipsum dolor sit amet consectetur adipiscing elit",
        "format": "plain_text"
      }
    }
  }
}

During the course of creating an article, you may also wish to include a relationship to a user to indicate that they were the one to create the article. The following payload reflects a relationship that ties the entity to an existing user ({{user_uuid}}) whose authorship is assigned in this POST request.

{
  "data": {
    "type": "node--article",
    "attributes": {
      "title": "My snazzy new article",
      "body": {
        "value": "Hello world! Lorem ipsum dolor sit amet consectetur adipiscing elit",
        "format": "plain_text"
      }
    },
    "relationships": {
      "uid": {
        "data": {
          "type": "user--user",
          "id": "{{user_uuid}}"
        }
      }
    }
  }
}

Both of these requests will result in a 201 Created response code with the JSON API response of the created entity with its generated UUID, just as if you had retrieved it as an individual resource. Here is a sample response in Postman:

image5_1.png

Note: If you receive a 403 Forbidden response code with a message requiring Administer nodes permissioning, assign that capability to anonymous users solely for testing purposes. Do not do this in production as it has security implications!

Updating resources with JSON API

PATCH requests also require the following request headers.5

Accept: application/vnd.api+json
Content-Type: application/vnd.api+json

To issue a PATCH request against Drupal that modifies an article, find the UUID of the article in question and append it to the end of the URL, as follows:

/jsonapi/node/article/{{node_uuid}}

And include a request payload that contains only the fields that you intend to modify and the UUID of the entity in question:

{
  "data": {
    "type": "node--article",
    "id": "{{node_uuid}}",
    "attributes": {
      "title": "My even snazzier new article"
    }
  }
}

Of course, just as we just did during creating our new article, we may want to modify this article to add authorship of a user that we identify via UUID in the request payload. The following payload reflects a relationship that ties the entity to an existing user ({{user_uuid}}) whose authorship is assigned in this POST request.

{
  "data": {
    "type": "node--article",
    "id": "{{node_uuid}}",
    "attributes": {
      "title": "My even snazzier new article"
    },
    "relationships": {
      "uid": {
        "data": {
          "type": "user--user",
          "id": "{{user_uuid}}"
        }
      }
    }
  }
}

Both of these requests will result in a 200 OK response code with the JSON API response of the updated entity. Here is the result of the request in Postman:

image3_6.png

Deleting resources with JSON API

We can now delete the article that we created, because we aren't quite satisfied with its snazziness, using the following request header.

Content-Type: application/vnd.api+json

 

With DELETE requests, unlike the other unsafe methods, there is no request payload required. We simply need to target the correct resource using its UUID and to issue a DELETE request against that URL.6

/jsonapi/node/article/{{node_uuid}}

This will result in a 204 No Content response code with an empty response payload, indicating that our article has been deleted. Here is what that looks like in Postman:

image6_0.png

Conclusion

As you can see, while JSON API is a relatively complex specification, the JSON API module makes the process of consuming it remarkably easy for new decoupled Drupal practitioners. In this column, we covered some of the background behind JSON API, the specification, and the module that is slated for inclusion in Drupal core. We also dove into how the JSON API module handles requests to retrieve content in various ways as well as to create, modify, and delete it.

Soon to come on the Experience Express, we'll be talking about two of the more intriguing web services solutions available in Drupal's contributed ecosystem: RELAXed Web Services and GraphQL. Also slated for arrival for upcoming editions is a roundup of DrupalCamp Spain in Alicante, where yours truly is presenting a keynote session. Tots a bord! ¡Todos a bordo! (All aboard!)

Works cited

  1. "JSON API." Drupal.org. 18 December 2017. Accessed 10 May 2018.
  2. Aguiló Bosch, Mateu. "Installing." YouTube. 10 February 2017. Accessed 10 May 2018.
  3. "Fetching resources (GET)." Drupal.org. 3 April 2018. Accessed 10 May 2018.
  4. "Creating new resources (POST)." Drupal.org. 22 March 2018. Accessed 10 May 2018.
  5. "Updating existing resources (PATCH)." Drupal.org. 29 May 2017. Accessed 10 May 2018.
  6. "Removing existing resources (DELETE)." Drupal.org. 13 May 2017. Accessed 10 May 2018.