Building rest endpoints with Drupal 8 is a snap. With the Rest UI module, fieldable entities, and the range of field-types in core, a site-builder can readily toss together a HAL+JSON REST server. Lists can be created with views using the REST export display type.
But any API is only as good as its documentation. Wouldn't it be great if Drupal could build out this documentation for us – and keep it current as we make changes?
REST API Documentation in the PHP World
Looking to our open source PHP cousins in Symfony, the NelmioApiDoc bundle generates documentation by parsing doc-blocks on each controller method.
But in Drupal, the REST endpoints are fully-dynamic methods in the REST module's controller. The REST and Serialization modules integrate with the Entity Field API to dynamically introspect the properties of each entity type and bundle. In theory, we should be able to do the same to build out our documentation by leveraging the information APIs in the Entity-Field and Routing APIs.
Providing Business Rules
We can use Drupal to introspect the available routes and the required fields for each end-point, but we can't capture business rules for each field. First we need to specify those rules for each field. Luckily, if we're doing things right, we've already accomplished this by adding help text when we created the field – so we’ll re-use that to build our documentation.
Finding the Endpoints
We first need to find the endpoints we wish to document. In the routing system, Drupal\Core\Routing\RouteProvider
is responsible for loading and returning routes. However, it doesn't provide a method to fetch routes matching a pattern; we need to know the route names. And in order to introspect all routes – and to return those that come from the Rest module and those from the Views module that relate to a REST export display – we need to get a list of route names that are prefixed with rest.
or view.
. Fortunately, the routing system fires events before new routes are saved and when route building is finished. To react to these events, we need an event listener. We also need access to the Entity Manager and Views executable services to introspect the Views module routes and see which contain a REST Export display.
We define our event listener using a service.yml
file, then tell Drupal that we want to react to the routing alter, and then route finished events. In the alter
event-listener, we inspect the routes and flag those relating to views with REST Export displays – or from the REST module itself – and track their names using the State Key-Value store. We now have the routes, so we can start creating documentation for each of them. The full contents of the subscriber can be seen at rest_api_doc.
Building the Documentation
We want our documentation overview to be at /api/doc
, and details for each route at /api/doc/{path}
, grouping together routes that share a common path for discoverability. We define those routes using a routing.yml
file, and build out a controller class to contain our callbacks. The overview method is fairly straightforward: we re-use the list of routes we collected earlier to present a table of contents; we expose a text-area field via a settings form to allow an administrator to enter some overview text; and we use the RouteProvider to return a discrete list of REST end-points by path for the given route names. The overview looks like this:
End-point Details
Each route that is based on an entity-type contains parameters relating to the content being sent or received. For example, a node end-point will contain the node-id for GET
, PATCH
, and PUT
operations. This is encapsulated in the parameters in the route path, as well as in the route requirements and options. We can inspect this detail in the routes using the Routing API, and use this to build out the documentation. By getting the parameters from each route, we can determine the entity-type. We can then examine the field and property data: Here it becomes a little convoluted.
Most traditional REST APIs would have an end-point for each type of object, and the properties of that object would be largely consistent. But Drupal has the notion of entity-type bundles. For the Node entity-type, these are represented by node-types. The fields that make up one node-type would ordinarily be completely different from the fields that make up another node-type. But from a REST perspective, with Drupal core, these two (or more) node-types share the same end-point. So the fields you must send for POST
, PUT
, and PATCH
operations – as well as the content you receive back from GET
requests – will vary wildly, depending on the bundle (node-type) of the item sent or requested. And, of course, it isn't limited to the node entity-type; taxonomy, comment, and blocks all have a notion of different fields for different bundles/types.
To get around this we list base-fields first, and then configurable fields specific to each bundle.
Summary
Drupal 8 is a leap forward in REST capabilities from Drupal 7: the Routing and Entity-Field APIs contain built-in information discovery. That helps developers quickly discover the properties and attributes of route and entity objects, and can also be used by Drupal to document itself.
All the code described here has been turned into the Self Documenting Rest API module. Let's continue the conversation in the issue queue for the project.
Image: "Blindfolded Typing Competition" by Foxtongue is licensed under CC BY-NC-SA 2.0