---
title: "Decoupling Drupal 8 Core: Core REST"
date: "2019-04-18T19:28:50+00:00"
summary:
image:
type: "article"
url: "/acquia-cloud-platform/help/91516-decoupling-drupal-8-core-core-rest"
id: "2bfd88af-1d92-4403-8d4f-e3fa99beb9a3"
---

Table of contents will be added

As we saw in a [previous installment of _Experience Express_](https://dev.acquia.com/blog/decoupling-drupal-8-core-core-rest-hal-and-setting-up-drupal-as-a-web-services-provider/29/03/2018/19311), because Drupal has a HAL-compliant REST API available out of the box with minimal configuration, you can easily provision an API that can immediately be employed to consume content entities and manipulate them from other applications. Now that we have successfully exposed content entities as REST resources, used Entity Access to govern permissions, and customized the formats and authentication mechanisms in use by the core REST API, it is now time to move into actually retrieving and manipulating that data.

Luckily, if you are familiar with other REST APIs, issuing HTTP requests against Drupal core to obtain the data you require in your application is simple. In this column, we will inspect the key components of most requests to the core REST API, how to retrieve and update content entities via core REST, and how to create and delete them.

Issuing REST requests against Drupal core
-----------------------------------------

Because REST is an architectural pattern that operates on HTTP, it makes use of HTTP verbs, which can be categorized into _safe_ and _unsafe_ methods. Often, however, this does not provide enough protection, so Drupal has provided an additional mechanism the _X-CSRF-Token request header_ to ensure that unsafe methods cannot be used nefariously. Finally, because Drupal flexibly serves responses across a variety of serialization formats, a query argument must be provided that contains the appropriate serialization format.

Safe and unsafe methods
-----------------------

In HTTP, verbs also known as request methods include `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `TRACE`, `OPTIONS`, `CONNECT`, and `PATCH`. Some of these request methods are safe in that they are read-only and cannot modify the data that they are retrieving. From the list above, safe methods include `HEAD`, `GET`, `OPTIONS`, and `TRACE`. Because all of the other methods are capable of performing write operations against the data exposed by the API, they are unsafe and can affect data stored in Drupal.

Among the methods listed above, we will only be considering `GET`, `POST`, `DELETE`, and `PATCH` in this blog post, because they correspond to the key CRUD (create, read, update, delete) operations that allow us to retrieve and manipulate content. In Drupal's case, `GET` means _read_, `POST` means _create_, `DELETE` means _delete_, and `PATCH` means _update_.1

While `PUT` in REST parlance also translates to updates of data, it is problematic because `PUT` requests typically include the entire data structure that will replace the existing data in the request body. As a result, requests that include not only a single content entity but also relationships to other entities introduce significant complexity to the API. Moreover, the HAL normalization in core includes link relations which must mirror the precise data returned when performing a `GET` request.

There are other Drupal-specific motivations for the lack of `PUT` support revolving around field-level permissioning on content entities. In order to perform a proper `PUT` request, the application performing the request would require write access on every field in a content entity rather than a select few. This means that permissions would potentially need to be expanded much more widely than expected. Fortunately, because `PATCH` supports partial write operations, updates to content entities can happen even when only certain fields are writeable by the consumer application in question.2

The `X-CSRF-Token` request header
---------------------------------

Cross-site request forgery (CSRF) is a scenario in which a consumer application with permissions to manipulate data behind the API could issue malicious requests against the API, even without the consumer application's knowledge. This is because the consumer application may not necessarily have the protections required to filter out potentially harmful data contained therein, particularly if the request contains user-generated data.

In order to protect itself from CSRF attacks, Drupal 8 obligates all requests to define an `X-CSRF-Token` request header when issuing a request that is sent using an unsafe HTTP method such as `POST`, `PATCH`, or `DELETE`. The content of this request header should be filled in with the retrieved token from the path `/session/token`. In examples below, we will see how the use of unsafe methods differs from safe methods due to the presence of the `X-CSRF-Token` request header.

Specifying serialization formats
--------------------------------

Because Drupal can flexibly handle multiple serialization formats, including HAL+JSON, JSON, and XML, all requests to the core REST API must include a query argument specifying the serialization format used in the request. This is the case even if only one serialization format is configured to be supported in the core REST API (such as JSON).

When issuing requests against Drupal's core REST API, you must append to each URI the query argument `?_format`. For instance, on the test site that we installed previously, issuing a `GET` request to retrieve HAL-compliant JSON for a node with an ID of 1 would require us to use the URI `core-rest.dd:8083/node/1?_format=hal_json`.

When a request body contains data in a particular serialization format, which is the case of unsafe methods such as `POST` and `PATCH`, you also need to specify the `Content-Type` request header with the chosen serialization method. The examples below demonstrate how.

Note: While `Accept` header-based content negotiation previously existed in Drupal 8, it was _removed from Drupal_ because of poor support on the part of browsers and proxies. Drupal 8 now requires the serialization format to be provided in query arguments rather than solely in the `Accept` request header.3

Retrieving content entities with core REST
------------------------------------------

If you do not currently have a test site like the one we set up previously, return to that [previous installment of Experience Express](https://dev.acquia.com/blog/decoupling-drupal-8-core-core-rest-hal-and-setting-up-drupal-as-a-web-services-provider/28/03/2018/19311) to set up a site like `core-rest.dd:8083`. (All subsequent paths presented here will be domain-relative.) In addition, the examples below make use of the [Postman HTTP](https://www.getpostman.com/) client, which allows us to issue requests more quickly and with a friendlier interface than other tools like cURL. We already retrieved a content entity using `GET` previously, but it bears repeating to show the process once more. Issuing successful `GET` requests against the core REST API requires the following REST resource configuration:

    granularity: resourceconfiguration: methods: - GET formats: - hal_json authentication: - basic_auth

In Postman, you can issue a `GET` request against `/node/1` with the query parameter `?_format=hal_json` resulting in `/node/1?_format=hal_json` without any headers and receive the following response with a `200 OK` status code. Success!

![Screenshot of a Postman interface showing a GET request with JSON response data, including links and a "value" key.](https://acquia.widen.net/content/f6rtnhixli/web/url_f508b5059efcee6e9724be365a2a9c7f.png?v=9f6c1813-239a-4d56-ae37-7ada48005a58)

Creating content entities with core REST
----------------------------------------

Issuing successful `POST` requests against the core REST API requires the following REST resource configuration:

    granularity: resourceconfiguration: methods: - POST formats: - hal_json authentication: - basic_auth

Before issuing our request, we must first craft our request body to include the data structure we want Drupal to use to create our new content entity. In order to do this with HAL+JSON, we will also need to include the correct `_links` key in the request payload. The easiest way to identify what value the `_links` entry needs to provide is issuing a `GET` request against the entity and studying the link relations in the response payload.

Importantly, the request payload should never include a UUID, as that is assigned by Drupal when the content entity is created.

`POST` requests in Drupal require two steps if you have not yet retrieved an `X-CSRF-Token`. First, using Postman, issue a `GET` request against `/rest/session/token`. In response, you will get a jumble of letters and numbers (e.g. `Eh1INrGyEUNBog5ZL2o-dHFPnLoseIKCcL35aVSGg94`); this is your `X-CSRF-Token` which should accompany every unsafe method you use.4 Copy it to your clipboard for future reference.

![API testing interface showing a GET request with headers, a token response, and status 200 OK.](https://acquia.widen.net/content/wcyv1h502g/web/url_11094819aba823b7907e85e9f26a5d40.png?v=ff95375c-25a2-4c88-aa59-07aa05a27b89)

To create a new article, for instance, we can craft a `POST` request that contains the data structure for a new article with the title _My snazzy new article_. First, we provide a request payload that contains a data structure that Drupal can interpret and use to create a new article:

    { "_links": { "type": { "href": "http://core-rest.dd:8083/rest/type/node/article" } }, "title": [ { "value": "My snazzy new article" } ], "type": [ { "target_id": "article" } ]}

![API request in Postman showing JSON body with article details, including title and type.](https://acquia.widen.net/content/jep4oa6xmk/web/url_3a7bc1c75c3577317794ed16f999cda7.png?v=ea594f14-b2ed-43c1-94ca-29cf0349f723)

At this point, because we are primarily focused on how to form a request, we will make our lives easier by not using any built-in authentication, which will be covered in a future installment. This is extremely dangerous and highly inadvisable live on production, but we can do this safely on a local production environment. While user-generated content is an exception, operations across the API by applications are typically riskier because it isn't possible for Drupal to control how or how much of that content is introduced.

For the purposes of this blog post, we'll enable an anonymous user to create content by navigating to _People > Permissions_ in Drupal's administration toolbar. Grant the following permissions to the _Anonymous user_ role on the appropriate content types (currently we are only working with _Articles_ and _Basic pages_): _Create new content_ (i.e. `POST`), _Delete any content_ (i.e. `DELETE`), and _Edit any content_ (i.e. `PATCH`).

Then, back in Postman, in the request headers, we will want to provide the `X-CSRF-Token` that we retrieved via `GET` earlier (`X-CSRF-Token: Eh1INrGyEUNBog5ZL2o-dHFPnLoseIKCcL35aVSGg94`) as well as the correct `Content-Type` header (`Content-Type: application/hal+json`).

![Postman interface showing a POST request with headers for X-CSRF-Token and Content-Type set to application/hal+json.](https://acquia.widen.net/content/2jh5qvsirs/web/url_bbbdff5216278479b0c95697200bc066.png?v=f23296a7-e464-42f0-a436-35e0cb5dc1bb)

In Postman, you can issue a `GET` request against `/node/1` with the query parameter `?_format=hal_json` resulting in `/node/1?_format=hal_json` without any headers and receive the following response with a `200 OK` status code. Success!

Now, in Postman, we can issue a `POST` request against `/entity/node?_format=hal_json` (recall that the query parameter is required for every request to Drupal). When we issue this request, we receive a `201 Created` response whose payload contains the data structure of the entity we just created. Success again!

Here is what we're given by Postman:

![Screenshot of JSON data in a code editor, displaying various URLs and values related to a REST API response.](https://acquia.widen.net/content/cdmjhvq7to/web/url_3662b4746be607f846a0a5352cfbda64.png?v=ccb68eeb-24e6-4039-96d5-a26b627c0496)

If we navigate to our home page, which currently displays content ordered by most to least recent, we can see our new article has been created by Anonymous. Because we did not provide anything in the body field, the article only has a title.

![Website interface showing "Core REST" with an article titled "My snazzy new article" submitted by Anonymous. Options include search, read more, and add comment.](https://acquia.widen.net/content/j7h4jfqnyp/web/url_316a76dd607f18f9361ef29bc395b2bb.png?v=99063361-5b8b-4fa3-8203-01d245174c5e)

Note: As of Drupal 8.3.0, it is possible to issue requests against the resource `/node` with the format query parameter included; for all intents and purposes that resource is identical to `/entity/node` in versions of Drupal 8.3.0 and later.4

Updating content entities with core REST
----------------------------------------

Now that we've created our snazzy new article, how do we update it in cases where our marketing team needs the title to change or our client would like slightly adjusted text? Issuing successful `PATCH` requests against the core REST API requires the following REST resource configuration:

    granularity: resourceconfiguration: methods: - PATCH formats: - hal_json authentication: - basic_auth

As we saw with `POST` requests, before issuing our request, we need to ensure that our request body contains the `_links` key to adhere to the HAL specification. Because the entity has already been created, the request payload should not include a UUID, as that is an immutable value within Drupal.

`PATCH` differs from `POST` in that whereas creating content requires us to provide every field we wish to represent in the created node, `PATCH` only obligates us to provide the _changes_ we want made to the entity. While this means that we have less data to transmit, and our requests can accordingly be smaller, it also presents several challenges due to the fact that a `403 Forbidden` response code will never be issued when one field is editable based on permissions but another one defined in the same request is not editable. As a result, some requests could succeed partially and record their failures silently.

On the other side of the spectrum, there are certain components of a `PATCH` request which must be present, even if that information is not changing, because the server needs an understanding of which bundle to refer to when it deserializes that information. As such, certain information, for instance the content type (e.g. _Article_ or _Basic page_), must be transmitted in the request.

Note: Since Drupal 8.1.0, all successful `PATCH` requests return a `200 OK` response status code with the serialized entity in the response body. Before Drupal 8.1.0, you would receive a `204 No Content` code with an empty body.5

To begin, we need to craft a `PATCH` request whose headers contain our `X-CSRF-Token`. In this example we've generated a new one (`i7GUIxfEYRR3nzcNyremz9Q73sdyTpStSoCsU7J0NQw`). Then, to update our existing article, we need to provide a request body that contains the fields we wish to change as well as the HAL-compliant `_links` key:

    { "_links": { "type": { "href": "http://core-rest.dd:8083/rest/type/node/article" } }, "title": [ { "value": "My snazzy and snappy new article" } ], "type": [ { "target_id": "article" } ]}

In this case, because we are only changing the title field (our title is changing to "My snazzy and snappy new article"), we don't need to provide any other information about other fields. The `target_id` field provides the content type that Drupal will need to use to deserialize this data structure.

However, because we are not creating a new entity, the resource we point the request to changes to the resource that we created in the previous section. This means that we need to acquire the identifier of the node (the nid) and target our `PATCH` request against that resource URL. In our case, because we created twenty nodes previously, that path becomes `/node/21?_format=hal_json`, as our created article acquired an nid of 21.5

First, we provide the appropriate headers.

![API request interface showing PATCH method, headers for X-CSRF-Token and Content-Type, and values for each in a table format.](https://acquia.widen.net/content/931ocdwjhq/web/url_279ebc0fa51e104547e42832a8bfd88e.png?v=baa16afd-37dc-423b-bb66-8041ab947a35)

Then, we attach our changes in the request body.

![Screenshot of a PATCH request in Postman, showing JSON data for updating an article with the title "My snazzy and snappy new article."](https://acquia.widen.net/content/m4gky4jwsl/web/url_90d6889dffcc389de93185b50f54c3cc.png?v=da0a88fd-6a14-4383-948d-5c16b8434dc7)

Here is what we receive in return, a `200 OK` response with the body containing the serialized entity, indicating a successful `PATCH`:

![Screenshot of a JSON response in an API testing tool, displaying links and metadata related to a node and user.](https://acquia.widen.net/content/tdewlvo8j1/web/url_b21b785631590bb85207ba13639acc0a.png?v=a8384195-494e-47f1-a936-099b1e1af1b1)

If we refresh the Drupal home page, we see that the article we created before has now been updated with our new title: pretty snazzy _and_ snappy!

![Screenshot of a webpage with a blue header, search bar, and article titled "My snazzy and snappy new article."](https://acquia.widen.net/content/0tj6uhv7co/web/url_3581079411d70cfd95b918ba98ce1cf5.png?v=b07ec7ce-eef6-4a9d-9484-8cc790214349)

Deleting content entities with core REST
----------------------------------------

As it turns out, our customer is not a fan of the new article, despite its snazziness and snappiness. They've requested that we delete the article so that it does not appear in the consumer application. All content must go somewhere once it is no longer relevant or needed. For that, we need `DELETE`. Issuing successful `DELETE` requests against the core REST API requires the following REST resource configuration:

    granularity: resourceconfiguration: methods: - DELETE formats: - hal_json authentication: - basic_auth

After retrieval of content with `GET`, `DELETE` requests may be the most simple to compose. This is because certain information is unnecessary; we do not need any deserialization on the Drupal back end, only an understanding of which entity we need to delete. In addition, because there is no request body, no data is ever transmitted, which means there is no need to have a MIME type encoded in a `Content-Type` request header.6

All we need to do is point our request at the resource in question with a suffixed query parameter `/node/21?_format=hal_json` and issue a `DELETE` request containing the `X-CSRF-Token` header. Here is what that request looks like; there is no screenshot of the request body, as that remains empty:

![API tool interface showing a DELETE request with X-CSRF-Token in headers, including key-value pairs for authorization.](https://acquia.widen.net/content/odgv6he0zi/web/url_576ecca397c25da99dcc09229979b5be.png?v=677288b2-2ebd-43dd-9d6d-6db6682e9d34)

Here is what we receive in return, a `204 No Content` response status code with an empty body indicating that our entity is now deleted:

![Postman interface showing a response with status 204 No Content. Headers, status, time, and size details are displayed.](https://acquia.widen.net/content/2pgn936ucv/web/url_13f7730993133e2e8bc6a2152e6e1707.png?v=9ee3c71e-e799-4175-b051-a203cf0f7ac9)

Sure enough, when we navigate to our Drupal home page again and refresh the page, we can see that the entity has now vanished! Success!

![Webpage interface with navigation bar, search box, and placeholder text under "Brevitus Incassum Quadrum." Includes options for content management and user account actions.](https://acquia.widen.net/content/nfhicfohwg/web/url_fafd01aac799e6355dfb28b045fce72c.png?v=507c55f8-72f5-438a-b5d4-30d27b35ceb3)

Conclusion
----------

In this blog post, I introduced you to the key approaches in retrieving and manipulating content entities in Drupal through the core REST API employing simple use cases such as modifying a title or deleting an entity. As you can see, working with the core REST API is quite simple as long as you have configured permissions and REST resources correctly. While Drupal certainly is a unique case, and while these examples were trivial in order to demonstrate a consistent process to API testing that we will reuse later, the immediacy and directness of the core REST API are part of what make it so appealing.

Next time on the Experience Express, we're off to Philadelphia, where [Drupaldelphia](https://drupaldelphia.org/) is part of the kickoff for [Philly Tech Week](http://phillytechweek.com/). I'll be joining up with several others on a keynote panel about some of the exciting work happening around the future of Drupal 8 in response to the Driesnote at DrupalCon Nashville. You'll probably catch me having a roast pork sandwich (with sharp provolone and broccoli rabe, of course) at some point during the day!

Works cited
-----------

1.  "[Getting started: REST configuration & REST request fundamentals](https://www.drupal.org/docs/8/core/modules/rest/1-getting-started-rest-configuration-rest-request-fundamentals)." Drupal.org. 17 May 2017. Accessed 2 April 2018.
2.  Garfield, Larry. "Putting off PUT." Drupal.org. 26 February 2013. Accessed 2 April 2018.
3.  Wehner, Daniel. "Accept header based routing got replaced by a query parameter." Drupal.org. 6 July 2015. Accessed 2 April 2018.
4.  "[POST for creating content entities](https://www.drupal.org/docs/8/core/modules/rest/3-post-for-creating-content-entities)." Drupal.org. 14 March 2018. Accessed 24 April 2018.
5.  "[PATCH for updating content entities](https://www.drupal.org/docs/8/core/modules/rest/4-patch-for-updating-content-entities)." Drupal.org. 9 November 2016. Accessed 24 April 2018.
6.  "[DELETE for deleting content entities](https://www.drupal.org/docs/8/core/modules/rest/5-delete-for-deleting-content-entities)." Drupal.org. 11 July 2017. Accessed 24 April 2018.