Playing around with the Drupal 8 JSON API as a data service

I’m familiar with the concepts of REST but have never done anything in practice before. I decided I wanted to end that and start messing with the JSON API in Drupal 8. It was surprisingly easy to get going with because of the incredible resources out there. In this multi-part series, I’m going to explore how to use Drupal 8 as a data back-end for a React app.

JSON API Drupal Playlist by Mateu Aguiló Bosch

This video series is an excellent job of succinctly explaining how the JSON API can be used to make basic requests to Drupal.

Postman is amazing

In the series, Mateu is using a tool called Postman (free!), that makes managing REST requests really easy. This is amazing for testing out your API before you hook it up to an application.

Using Postman, for requests that require authentication, you can set up basic authentication (which uses your Drupal username and password) for easy testing purposes.

The examples below were all run with Postman. I decided it would most likely be useful to someone if I showed how I POSTed data to a content type using the JSON API.

Potential gotcha
Make sure in your request headers, you are specifying the Content-Type as application/vnd.api+json. This is according to the JSONAPI spec. Not doing so will throw a 422 Response from the server.

My app use case

I’m building a workout routine React application. The main idea is that users have Routines which contain Timed Tasks (could be yoga postures, jumping jacks, whatever). The Routines hold references to the Timed Tasks. The user can create a Routine of Timed Tasks, and then “play” through the routine, which will prompt the user to perform a workout action.

Timed Task content type

  • Title
  • Body
  • Task Setup
    • Minutes
    • Seconds
  • Task Duration
    • Minutes
    • Seconds
  • Task Rest
    • Minutes
    • Seconds

Routine content type

  • Title
  • Body
  • Timed Task entity reference

Now, lets use Postman to test some basic REST calls that I’m sure my React app will have to make.

Examples:

Local site url: http://drupal-8-3-0.dd:8083

Adding TimedTask content

POST -> http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task

Body

{
   "data":{
      "type":"node--timed_task",
      "attributes":{
         "status":true,
         "title":"Half Moon right side",
         "body":{
            "value":"This pose is tough",
            "format":"basic_html"
         },
         "field_setup_minutes":{
            "value":0
         },
         "field_setup_seconds":{
            "value":30
         },
         "field_task_minutes":{
            "value":1
         },
         "field_task_seconds":{
            "value":0
         },
         "field_rest_minutes":{
            "value":0
         },
         "field_rest_seconds":{
            "value":20
         }
      }
   }
}

Response

201 Created

{
  "data": {
    "type": "node--timed_task",
    "id": "ad4a8609-1b98-436c-9efd-fc9fe91ad908",
    "attributes": {
      "nid": 56,
      "uuid": "ad4a8609-1b98-436c-9efd-fc9fe91ad908",
      "vid": 56,
      "langcode": "en",
      "status": true,
      "title": "Half Moon right side",
      "created": 1491878234,
      "changed": 1491878234,
      "promote": false,
      "sticky": false,
      "revision_timestamp": 1491878234,
      "revision_log": null,
      "revision_translation_affected": true,
      "default_langcode": true,
      "content_translation_source": "und",
      "content_translation_outdated": false,
      "path": null,
      "body": {
        "value": "This pose is tough",
        "format": "basic_html",
        "summary": null
      },
      "field_rest_minutes": 0,
      "field_rest_seconds": 20,
      "field_setup_minutes": 0,
      "field_setup_seconds": 30,
      "field_task_minutes": 1,
      "field_task_seconds": 0
    },
    "relationships": {
      "type": {
        "data": {
          "type": "node_type--node_type",
          "id": "d1dd56a7-af44-4c01-931d-b441da7f40dc"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/relationships/type",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/type"
        }
      },
      "uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/relationships/uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/uid"
        }
      },
      "revision_uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/relationships/revision_uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908/revision_uid"
        }
      }
    },
    "links": {
      "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task/ad4a8609-1b98-436c-9efd-fc9fe91ad908"
    }
  },
  "links": {
    "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/timed_task"
  }
}

There’s a lot of stuff in there that we don’t need, but this piece is of interest:

"attributes": {
  "nid": 56,
  "uuid": "ad4a8609-1b98-436c-9efd-fc9fe91ad908",
  "vid": 56,
  "langcode": "en",
  "status": true,
  "title": "Half Moon right side",
  "created": 1491878234,
  "changed": 1491878234,
  "promote": false,
  "sticky": false,
  "revision_timestamp": 1491878234,
  "revision_log": null,
  "revision_translation_affected": true,
  "default_langcode": true,
  "content_translation_source": "und",
  "content_translation_outdated": false,
  "path": null,
  "body": {
    "value": "This pose is tough",
    "format": "basic_html",
    "summary": null
  },
  "field_rest_minutes": 0,
  "field_rest_seconds": 20,
  "field_setup_minutes": 0,
  "field_setup_seconds": 30,
  "field_task_minutes": 1,
  "field_task_seconds": 0
}

The core of the TimedTask is structured nicely for me to use in a front end framework like React.

Now, I want to be able to store a collection of these TimedTasks in a collection known as a Routine. A Routine will consist of many TimedTasks that can be rearranged.

I added 3 more Timed Tasks and noted their UUIDs in the response.

  • ad4a8609-1b98-436c-9efd-fc9fe91ad908
  • 390de987-11b2-47b4-a692-3c160bccab0a
  • 760a6c82-b0f1-4b2a-93d4-140061e878a9
  • 6548f256-a518-491a-8bba-d469778a3722

Now, lets add these as entity references to a Routine content type.

POST -> http://drupal-8-3-0.dd:8083/jsonapi/node/routine

Body

{
   "data":{
      "type":"node--routine",
      "attributes":{
         "langcode":"en",
         "status":true,
         "title":"My Yoga Routine :)",
         "body":{
            "value":"<p>This is my morning workout :D</p>\r\n",
            "format":"basic_html",
            "summary":""
         }
      },
      "relationships":{
         "field_timedtasks":{
            "data":[
               {
                  "type":"node--timed_task",
                  "id":"ad4a8609-1b98-436c-9efd-fc9fe91ad908"
               },
               {
                  "type":"node--timed_task",
                  "id":"390de987-11b2-47b4-a692-3c160bccab0a"
               },
               {
                  "type":"node--timed_task",
                  "id":"760a6c82-b0f1-4b2a-93d4-140061e878a9"
               },
               {
                  "type":"node--timed_task",
                  "id":"6548f256-a518-491a-8bba-d469778a3722"
               }
            ]
         }
      }
   }
}

Response

201 Created

{
  "data": {
    "type": "node--routine",
    "id": "5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1",
    "attributes": {
      "nid": 62,
      "uuid": "5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1",
      "vid": 62,
      "langcode": "en",
      "status": true,
      "title": "My Yoga Routine :)",
      "created": 1491879173,
      "changed": 1491879173,
      "promote": false,
      "sticky": false,
      "revision_timestamp": 1491879173,
      "revision_log": null,
      "revision_translation_affected": true,
      "default_langcode": true,
      "content_translation_source": "und",
      "content_translation_outdated": false,
      "path": null,
      "body": {
        "value": "<p>This is my morning workout :D</p>\r\n",
        "format": "basic_html",
        "summary": ""
      }
    },
    "relationships": {
      "type": {
        "data": {
          "type": "node_type--node_type",
          "id": "d50564ec-fb5e-4031-866a-38e8ca98dc0c"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/type",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/type"
        }
      },
      "uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/uid"
        }
      },
      "revision_uid": {
        "data": {
          "type": "user--user",
          "id": "a552daf1-9feb-47cf-958a-a50329b7d689"
        },
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/revision_uid",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/revision_uid"
        }
      },
      "field_timedtasks": {
        "data": [
          {
            "type": "node--timed_task",
            "id": "ad4a8609-1b98-436c-9efd-fc9fe91ad908"
          },
          {
            "type": "node--timed_task",
            "id": "390de987-11b2-47b4-a692-3c160bccab0a"
          },
          {
            "type": "node--timed_task",
            "id": "760a6c82-b0f1-4b2a-93d4-140061e878a9"
          },
          {
            "type": "node--timed_task",
            "id": "6548f256-a518-491a-8bba-d469778a3722"
          }
        ],
        "links": {
          "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/relationships/field_timedtasks",
          "related": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1/field_timedtasks"
        }
      }
    },
    "links": {
      "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine/5075e4b7-e2d6-49d0-ba65-c0b4b48fc8f1"
    }
  },
  "links": {
    "self": "http://drupal-8-3-0.dd:8083/jsonapi/node/routine"
  }
}

Using Postman, I helped validate the basic API structure that my React app will use. Next, I’m going to look into how we can hook this up into a React application.

What do you think?