An Introduction to RESTful Web Services in Drupal 8

Note: This article was updated on April 14, 2016 by trainer Blake Hall and tested with Drupal 8.0.6.

One of the Drupal 8 initiatives that really excites me is Web Services. Drupal has never been easy to work with as a web service, but all that is about to change! In this article I am going to explore what has been going on behind the scenes with RESTful Web Services in Drupal Core and attempt to implement some working examples. After reading, you will be able to create a new node on your site via the Drupal 8 Core REST API.

What is REST?

It's best that I first make sure everyone is on the same page. Web Services make it possible for external applications to interact with our application (in this case our Drupal site). The most common interactions are reading, creating, updating, and deleting resources.

REST is one of the most popular ways of making Web Services work. There are other formats such as SOAP or XML-RPC, but we are only going to focus on REST because it is the Drupal standard. REST utilizes HTTP methods, such as GET, POST, and DELETE.

As an example, a popular usage of a REST interface is a mobile application that needs to read and write data from your site's database.

Drupal 8 Core

Web Services have been implemented in Drupal 8 Core with the following modules:

RESTful Web Services (rest)

Exposes entities and other resources via a RESTful web API. It depends on the Serialization module for the serialization of data that is sent to and from the API.

Serialization (serialization)

Provides a service for serialization of data to and from formats such as JSON and XML.

Hypertext Application Language (hal)

Extends the Serialization module to provide the HAL hypermedia format. This is what is used as the primary format in Drupal 8 Core. It only adds two reserved keywords, ‘_links’ for link relations (also used by Github's Pull Request API) and ‘_embedded’ for embedded resources. The HAL hypermedia format can be encoded in both JSON and XML. For more details see the initial HAL proposal.

HTTP Basic Authentication (basic_auth)

This module implements basic user authentication using the HTTP Basic authentication provider. It facilitates the use of an username and password for authentication when making calls to the REST API. It is required for the examples shown in this blog post, and I would advise configuring SSL if you use it in production. For anyone looking for a more secure option, check out the contributed OAuth module which already has a Drupal 8 release.

Getting started

Start by grabbing the latest release of Drupal 8 Core and installing your site. I am using 8.0.6 at the time of writing this post. See the Releases for Drupal Core to find the latest download.

Enable the following modules: REST, Serialization, HAL, and HTTP Basic Authentication.

Web services module to enable

By default, the REST module enables the node entity resource for all GET, POST, PATCH, and DELETE operations. It supports basic or cookie authentication and the HAL or JSON formats. You can see these default settings in sites/default/files/config_XXXX/active/rest.settings.yml. To enable REST on other entities (e.g. users, files, or fields), you'll need to edit this file. There is, however, a handy contributed module created by our very own Juampy named REST UI. This module provides a user interface for enabling and disabling resources, serialization formats, and authentication providers. See the screenshot below for an example of the configuration options REST UI provides.

REST UI configuration options

Let me take this opportunity to explain the available resource options, GET, POST, PATCH, and DELETE. These are all common HTTP methods. Each operation signifies a type of action to be performed on a resource. For example, your browser used GET when you requested to view this blog post. To read a resource, we GET. To create a resource, we POST. To update a resource, we PATCH. And to delete a resource, we DELETE. Read this post if you're wondering why we use PATCH and not PUT.

Now back to our setup. For all enabled resources, the REST module can set user permissions. Go to admin/people/permissions and set up your permissions, as required. Here is an example of how I set mine:

Permissions

You should create also a new authenticated user for testing purposes. If you test your API with User 1 administrator account credentials, you will likely miss permission settings that you need to be set.

Using the REST API

Now we're ready to start using our REST API. I recommend the Dev HTTP Client Chrome extension for testing calls to any API. You can also write scripts in Guzzle, which is a great new tool included in Drupal 8 Core or cURL via the command line or PHP.

Start by creating a node entity. To do this, we must send a POST to /entity/node with the Content-Type header set to application/hal+json and declare the required type and title fields in the request BODY. But don't forget that because we are using Basic Auth, we need to set the headers PHP_AUTH_USER and PHP_AUTH_PW to authenticate as our user. Here is how it looks in the Dev HTTP Client:

POST error

Or, if you prefer, you can try cURL from the command line:

curl --include --request POST --user drupaljoe:mango --header 'Content-type: application/hal+json' http://localhost:8082/d8/entity/node?_format=hal+json --data-binary '{"_links":{"type":{"href":"http://localhost:8082/d8/rest/type/node/page"}}, "title":[{"value":"My first page"}]}'

You'll notice the request is returning 403 Forbidden. It's important to remember that although you have given authenticated users permission to Access POST on Content resource they still need permission to perform the underlying action which, in this case, is creating a new page node. So head back to the permissions page and make sure authenticated users have at least the Page: Create new content, Page: Edit own content and Page: Delete own content permissions.

Another recent addition is the requirement of a CSRF token that needs to be sent along with your POST request. You can get this token by making a GET request to rest/session/token. It's also worth noting that this token is user specific, so any nodes created by POST requests with this token will be authored by the user associated with this token.

Try again, adding an additional X-CSRF-Token header with your token, and you'll now get a nice green 201 Created response. Congratulations! You just created a node entirely via your new REST API!

You can now GET the node we just created. Send a GET to /node/<nid>?_format=hal+json and you should receive a 200 OK with the BODY containing a HAL representation of the entire node entity. For a simpler JSON representation, set the _format query parameter to json and compare the results.

You should now have a general understanding of REST in Drupal 8 and the basics behind manipulating single Drupal entities via the REST API. We only used POST and GET, but PATCH and DELETE work similarly. Feel free to try them in your own time!

I hope to write more posts about REST in Drupal 8 on topics such as setting custom entity field values and retrieving lists of entities using Views. I'd like to look also into setting up more secure REST API authentication using modules such as OAuth.

Update: My blog post on RESTful Views in Drupal 8 has been released.

Please leave your comments and queries below and we'll do our best to answer what we can!

Further reading

Related Topics: 

Comments

Great post!!

Thank you very much for sharing.

Just one typo in the line regarding "(Read this post if you’re wondering why we use PUT and not PATCH.)". I guess you mean the opposite: "(Read this post if you’re wondering why we use PATCH and not PUT.)"

Regards.

Aha, I confused even myself on that one. Thanks very much Ruben, I've updated the article text.

Nice introduction, thanks. The only thing I don't understand is why the node type is defined as a "_links" property referring to a URL. Simply {"type":"page"} would have made sense IMHO...

You can use the simple JSON format which would allow you to write syntax that way. However, the default mediatype format in Drupal 8 is HAL. You can read up more about HAL here: http://stateless.co/hal_specification.html

 

My understanding is that HAL offers developers that use the API more insight into navigating around the API therefore making it easier to work with and more appealing. I would also recommend watching the REST and Serialization in Drupal 8 DrupalCon Prague 2013 presentation as they touch on this topic too.

Thanks!

I wonder if you meant to talk about Guzzle Oauth (https://drupal.org/project/guzzle_oauth) instead of Oauth? If not, I'd love to know why exactly?

Guzzle OAuth seems to be for signing requests to other external APIs with our OAuth credentials. For example, as shown on the modules front page, when calling the Twitter API you can use Guzzle OAuth to easily add your API credentials.

 

What we want to do here is use the OAuth module to set up server side authentication of other users calling our own website via our REST API.

This is a great overview - thanks !

Any idea if the Field API has been cleaned up a bit in D8? A big headache with services in D7 was the data structure was all over the place. I had to use Tamper Data Firefox plugin to see exactly what was expected in the POST data to CREATE/UPDATE entity fields using the services module. It was also nearly impossible to establish a pattern to automate client side editing to send through to Drupal Services

Hey Marcus. I'm not actually sure how accessible the Field API is through REST in Drupal 8 yet. It's something I hope to dive in soon so if I do figure something out, I'll let you know!

Great post!! My understanding is that HAL offers developers that use the API more insight into navigating around the API therefore making it easier to work with and more appealing. Thank you very much for sharing.

This is great. It got me headed in the right direction. I have been working with AngularJS but am having trouble figuring out the exact syntax to use for my GET to be sent to my Drupal 8 site. Any idea where some tutorials specific to tying AngularJS/Drupal 8 REST might reside? Thanks.

I'm not too familiar with how AngularJS builds GET requests but the $http service might be what you need. http://docs.angularjs.org/api/ng/service/$http

hi mr, I don't know drupal at all, but I have been assigned to rebuild ums.ac.id using drupal, and I have experience in developing php and angularjs before, how can I get started in learning drupal 8 and angularjs?

Remember Drupal 8 is not done yet. To get a leg up though, we have this guide to get you started - https://drupalize.me/guide/preparing-drupal-8. As for angular, we currently don't have any material but it is in the works.

Hi,

I just wanted to see how the custom resources (especially the POST method) works in the Drupal 8 RESTful services.

Let's say i have a custom table 'xyz' and i just wanted to create a RESTful resource which will GET and POST data to these tables. Can you please show me some pointers in this?

Hi Nagarajn, you can indeed turn a custom module into a RESTful resource. You would need to create a 'resource plugin' like the rest module in core does for entities out of the box. There isn't much documentation out about this at the moment but there is a couple of slides on the subject by Lin Clark that might prove useful. http://lin-clark.com/d8-rest-slides/#36

Alternatively, if you can turn your custom data into an entity then you wouldn't need to write your own plugins. Good luck!

Thanks! I have created the custom resource using the 'resource plugin' and i can view the data fro the GET method. But when it comes to PST method, am getting the error saying "error":"Could not denormalize object of type , no supporting normalizer found."}

Also the Lin Clark's slides gave me a good start.

I couldn't be sure but it sounds like you may have an issue with data serialization. Sorry, I'm yet to write my own custom resource to accept POST requests so can't give you much insight!

Please change URI to /node/1 instead of entity/node/1
Since https://drupal.org/node/2199185 happened. Update the article :)
Thank you.

Thank you! I've updated the article.

This is great! How do I send a file (such as an image) to a node?

Jens R, this is not possible yet, but you can follow this issue where it is being worked out https://www.drupal.org/node/1927648

I'm not sure if this changed since this blog post was written, but I was unable to post a node without adding a "X-CSRF-Token" header with a token which can be acquired through /rest/session/token.

Thanks,Very useful information

Your article helped me a lot to get started. Howeve, with beta12 or the current dev download, I only get HTML output, even if a I disable the REST access. I have learned to append a "?_format=json" to the URL to get JSON format - that helps. But I would like to understand wether I am doing anything wrong?

I have a freshly configured Drupal 8.0 beta 12 with HAL, HTTP Basic Authentication, RESTful Web Services and Serialization modules enabled. The installation is in a subfolder (D8_beta12). When I access the web services for the first page created (id=1) like

curl -H "Accept: application/json" --request GET http://localhost/d8_beta12/node/1

DRUPAL only delivers HTML format of the node. I have tried to configure via REST UI to JSON only but that didn't help. DRUPAL would answer the requests even if the web services are disabled.

Its probably the user, who's sitting in front of DRUPAL, who is the problem (me) - but I don't get it. Any help deply appreciated - I have been working hours on this, reviewing all of DRUPALs forums and here as well.

Hi Andi,

I've noticed some issues with this as well. From my testing it appears a regression was introduced in core that broke this behavior between beta 10 and beta 11. If you roll you local back to beta 10 you should be able to get json responses.

I haven't been tracking core development closely enough to point you to an in progress fix, but hopefully it'll be resolved by the next beta release.

Cheers,

Blake

@Andi starting with Drupal8 Alpha 12, core no longer uses accept headers for routing but rather rely on a _format query parameter. So your curl command should look like

# curl -i http://localhost/d8_beta12/node/1

notice I didn't add "--request GET" as by default curl GETS ;)

Thanks for the clarification @likewhoa!

I'm currently using REST on beta12 at the moment with no problems at all. In fact its quite straightforward to add additional operations beyond GET, POST, PATCH, DELETE. What you need for POST, PATCH or other custom operations which need to send data is a correctly formatted data "package" using the format which you declared, ie. hal+json. I have a content entity which creates a table in the database called "intermed". When I wish to send data via REST, the data package I build in beta12 looks as follows:

{
"_links":{
"self":{
"href": "http://beta12.qetm.org/intermed"},
"type":{"href": "http://beta12.qetm.org/rest/type/intermed/intermed"}
},
"script_name":[{"value":"default-script-name"}],
"script_arg":[{"value":"default-arg"}],
"script_content":[{"value":"//default-content"}],
"tip_number":[{"value":999999}],
"video_number":[{"value":999999}],
"video_id":[{"value":"default-id"}],
"width":[{"value":999}],
"height":[{"value":999}],
"script_type":[{"value":"apicmdscript"}]
}

The request for a PATCH operation for record 9 in the "intemed" table would be:

http://beta12.qetm.org/intermed/9?_format=hal_json

A POST operation for a new record would be:

http://beta12.qetm.org/entity/intermed?_format=hal_json

For POST and PATCH you will need to send 4 headers: Authorization, Accept, Content-Type, X-CSRF-Token

The token for your site's REST environment can be obtained by entering the url "/rest/session/token".
Authorization value will be your username/password.
Both Accept and Content-Type headers will have values: "application/hal+json".

If I was using the built-in "content" entity then the data package would be:

{
"_links":{
"self":{
"href": "http://beta12.qetm.org/node"},
"type":{"href": "http://beta12.qetm.org/rest/type/node/page"}
},
"title":[{"value":"My Node Page Title"}],
"body":[{"value":"This is the body content of my page"}]
}

Hopefully this helps.

Thanks for this. Do you know how one might send headers of a logged in user? I'm really struggling with this. I wrote http://drupangular.srgunltd.co.uk on AngularJS and I'm hardcoding a base64 user/pass - and can't seem to find how I'd send a dynamic one anywhere. Any ideas?

Hi,
thanks for this tutorial. Now, I´m using the drupal 8 beta 15 and i do not get any json-formatted Node by any Request. I give the permission for get to andybody and the other operations are for authenticated users and administrators. I´ve activated json, xml and hal+json for all operations (GET/POST/DELETE/PATCH). Now, i want to get a node as json. So, i tested it with the DHC client and a accept: application/json header, but i only get the html response. Maybe you have a solution for this problem. The url i´m using is "http://localhost/drupal8test/node/1" .

JensMander I am having the same issue. I can only get HTML returned.

It's a bit confusing, but a somewhat recent change when in that now requires you to specify the format in a query parameter.

If you'd like Drupal to return json, try adding ?_format=hal_json to the url of your request.

How can I create a user account from Rest?

Hi
I'm trying to identify the minimum set of data I need to send in to create a Comment against a node in Drupal 8 over REST.

I have successful POSTs and GETs for a Node working against:
/entity/node

I can GET against:
/rest/comments

Just problems with knowing what to POST for a Comment

For a comment I would expect it to be:
{"subject":"comment test",
comment_body:"bodyText",
entity_id:50
}

Any idea what I'm missing?

Hi Simon,

It looks like there might be an actual open bug against Drupal 8 core with this issue: https://www.drupal.org/node/2300827

There are a couple of patches, and examples of successful requests in that thread. Hopefully they're able to help until the bug in core is patched.

and you can edit the response PATCH?
in case of success or failure?

Is it possible to create a referrenced node from within a POST request ?

Hi Alex,

Are you trying to populate an entity reference field, or create the node that needs to be referenced, and the reference field update in one request?

Hi, thanks for this great tutorial.
After following all your indications I was stuck on the 403 error but this http://drupal.stackexchange.com/a/185907/14018 helped me.

Cheers

FYI - this https://www.drupal.org/node/2599364 can be useful if you are dealing with custom content types.

Hello,

I can only HTML returned with URL like /node/{id} (with headers Accept and Content-Type). I tried to add ?_format=jal_json to the url but I have response "Not acceptable".
I use version 8.0.4.
Can someone help me please ?

anthonyherve,

It looks like there's a typo in your format query parameter. Give ?_format=hal_json a try. If you're still having problems, make sure you have your REST services configured with the correct permissions.

I've found the RestUI module (https://www.drupal.org/project/restui) to be quite helpful in making sure I have everything set up correctly.

For more information about the Accept -> _format change, see https://www.drupal.org/node/2501221

hi,we provide online training & video tutorial for soapui
for free videos refer
http://soapui-tutorial.com/soapui-tutorial/introduction-to-webservices/

I'm hoping you can possibly help.

My permissions are set up correctly, so are my headers, but no matter what I do, I get a 403 forbidden error. This happens when I'm trying to obtain the rest/session/token and when I try to post (which is obviously then missing the token). It seems like it's not logging me into the site at all. I know the credentials are correct, I'm able to login to the site via my browser.

I apologize if this is a stupid question, but what am I missing?

Hi wihan,

I'm sure it's really frustrating to get a 403 when trying to request the token. I set up a sandbox site today to try and replicate this issue and couldn't. At this point I'd recommend going over your site again, making doubly sure you've enabled the required modules and properly configured permissions. If you're still experiencing issues reporting your problem in the Drupal core issue queue is probably the next step. They'll require detailed steps required to reproduce the issue, but if it is indeed an issue with core I'm sure it can be tracked down.

Add new comment