For developers of Drupal-backed JavaScript applications, simply having an HTTP client which can make requests against Drupal 8’s core REST API is often not enough. JavaScript developers are sometimes stymied by nuances unique to Drupal which leak out when consuming Drupal’s REST API, such as Drupal-specific paths for resources and confusing data structures in responses. Waterwheel.js alleviates these barriers to JavaScript developers by providing an additional API atop Drupal REST which allows you to retrieve and manipulate content entities in Drupal 8 without needing to deeply understand Drupal’s internals. And thanks to the indefatigable efforts of Matt Grill, Waterwheel 1.0 has been released today as part of the DrupalCon Dublin contribution sprint! In the first blog post in this series, Kyle Browning and I introduced the Waterwheel ecosystem, which is an emerging community surrounding SDKs which accelerate the development of Drupal-backed applications in JavaScript and Swift. In this blog post, I discuss some of the fundamentals of leveraging Waterwheel.js as an accelerator for your Drupal-backed JavaScript applications, whether they are progressively or fully decoupled.
Getting started with Waterwheel.js
To install Waterwheel.js, clone the GitHub repository using the following command. The master branch contains Waterwheel.js 0.8.1, which is the most up-to-date version. To use experimental features at your own risk, you can check out the 0.9.0 branch, which contains ongoing development work. $ git clone [email protected]:acquia/waterwheel-js.git
To install dependencies which are required when invoking Waterwheel.js methods, you can use the following shorthand command to install packages such as axios and qs. $ npm i
To build a browser version which you can include as part of a client-side JavaScript bundle, you can use the following command. This will create a file in the dist directory which contains all of Waterwheel.js’ functionality. $ npm run build
Integrating Waterwheel.js with Drupal 8
If you are employing Waterwheel.js in a fully decoupled setting where the Drupal-backed application is on a domain different from that of Drupal, you will need to set up cross-origin resource sharing (CORS). In Drupal 8.2, opt-in CORS support is now available. This means that you can set up CORS support within Drupal rather than adding headers in your .htaccess file on Apache or Nginx. In addition, the Waterwheel Drupal module is highly recommended for use with Waterwheel.js. Although it is not a strict dependency, some of the methods in Waterwheel.js will not work without the corresponding module (note that this is not true of Waterwheel.swift). The Waterwheel Drupal module is intended to track slightly ahead of core REST progress and bring certain features, such as resource discovery (see next blog post in this series), to Drupal sites before they are proposed or incorporated into core.
Instantiating Waterwheel
To instantiate Waterwheel.js on a Node.js server that includes ES6 support, use the const keyword in ES6 to require the Waterwheel module. Thereafter, you can instantiate a new Waterwheel object by providing it a base Drupal site URL and an OAuth2 token. // On a Node.js server const Waterwheel = require(‘waterwheel’); const waterwheel = new Waterwheel({base: 'http://drupal.dd, credentials: {oauth: '12345'}});
If you want to make Waterwheel available on your browser with ES6 support, you can import the generated bundle (see above) and attach it to the window object: // On a browser supporting ES6 import '../../path/to/node_modules/waterwheel/dist/waterwheel.js'; const waterwheel = new window.Waterwheel({base: 'http://drupal.dev', credentials: {oauth: '12345'}});
If your browser doesn’t support ES6, you can include the asset in a traditional tag before defining a Waterwheel global:
// On a browser not supporting ES6
var waterwheel = new window.Waterwheel({base: 'http://drupal.dev', credentials: {oauth: '12345'}});
The instantiation of Waterwheel also supports a property consisting of resources which can be supplied to your Waterwheel-driven application. See “Populating and accessing resources in Waterwheel.js” below for more information. In addition, you can provide a timeout argument, which defines for Waterwheel how long an HTTP request should be permitted to idle before being cancelled.
Resource discovery
The Waterwheel Drupal module introduces a new feature known as resource discovery, which enables client-side applications to understand the Drupal content schema without relying on responses from the server. After all, one of Drupal’s key features is the customizability of the content model, meaning that new content types and corresponding fields can be added arbitrarily. Client-side applications are unaware of the structure of these content types and fields, and without an understanding of how these entity resources are serialized, front-end developers are ill-equipped to include features such as client-side validation against a Drupal content schema and client-specific additions to the content model which reside alongside what exists in Drupal.Populating resources in Waterwheel.js
As mentioned above, if you wish to supply these resources dynamically in order to develop Waterwheel-driven applications in the complete absence of a Drupal site, you can pass resources in as an additional argument upon instantiating Waterwheel. This can, for instance, be provisioned by requiring an additional JSON file.// On a Node.js server
const Waterwheel = require(‘waterwheel’);
const waterwheel = new Waterwheel({base: 'http://test.dev', credentials: {oauth: '12345'}, resources: require('./resources.json')});
If you do not provide an additional resources argument upon instantiating Waterwheel, you can populate resources for Waterwheel through an additional API call to a resource provided by the Waterwheel Drupal module. This is something available to you in the populateResources() method, which can be invoked as seen below using ES6 promises:
waterwheel.populateResources()
.then(res => {
/*
[ 'comment',
'file',
'menu',
'node.article',
'node.page',
'node_type.content_type',
'query',
'taxonomy_term.tags',
'taxonomy_vocabulary',
'user' ]
*/
});
Manually adding new resources
You can also manually add resources to Waterwheel. This could be useful if you wish to have other entity structures alongside your Drupal entities, such as from another external content store. In this case, the parent object key will be used as the new resource’s name, and you can add an arbitrary number of custom resources as additional arguments:waterwheel.addResources(
{myNewResource: {
base: {{ base url }},
credentials: {{ credentials }},
methods: {{ methods }},
entity: 'node',
bundle: 'page',
resourceInfo: {{ extended information path }}
}}
);
The properties of the resources consist of the following, as quoted from the README:
base:
The base path for your Drupal instance. All requests for this resource will use this path. This can be different from the path used when instantiating waterwheel.credentials:
An object containing the username and password used to authenticate with Drupal. This can be different from the credentials used when instantiating waterwheel.methods:
An object containing the following keys: GET, POST, PATCH, DELETE. Each key should contain the path suffix that the action can be performed on.entity:
The entity type that this resource should reference, i.e. node.bundle:
The bundle that this resource should reference, i.e. page.options:
The path used to get extended (field) information about the bundle. This is usually provided automatically by the Waterwheel Drupal module but can be manually specified.
waterwheel.getAvailableResources()
.then(res => {
/*
[ 'comment',
'file',
'menu',
'node.article',
'node.page',
'node_type.content_type',
'query',
'taxonomy_term.tags',
'taxonomy_vocabulary',
'user' ]
*/
});