Skip to content

Tide API

Content API is built from a set of contributed modules and custom code provided through Tide API module.

Why JSONAPI?

Why we selected JSONAPI standard over generic REST provided by Drupal core:

  • JSON is a standard of REST, a subset of generic REST rules.
  • JSONAPI allows to query individual items and collections. Drupal JSONAPI module automatically exposes entities as endpoints. Because most of functionality is implement as entities in Drupal 8, there is no need for custom code to expose required features.
  • JSONAPI is a newer format
  • JSONAPI will be included in Drupal core. Documentation on using Drupal JSONAPI module: https://www.drupal .org/docs/8/modules/json-api

It is assumed that API consumers support the following:

  • Can traverse data.
  • Can cache based on response headers.
  • Can resolve relationships by following links.

jsonapi - main module that exposes entities as endpoints and provides support for REST operations.

jsonapi_extras - helper module to alter JSONAPI config: endpoints prefix (we are using /api/v1) and enable/disable endpoints for automatically exposed entities (we use this to limit access to internal entities).

Endpoints example

While providing a list of all endpoints would be to publish whole Drupal API (which is neither maintainable no required), the example page below demonstrates how Frontend Website can be mapped to Content Repository endpoints.

Page built using API data

Component Content API endpoint Comment
1 route /api/v1/route?alias=<alias> Given an alias, returns information about current route, including entity UUID.
2 main menu /api/v1/menu_link_content/menu_link_content?filter[menu_name][value]=main
3 banner TBD If exposed as a block - use block-based endpoint. If exposed as a content field - use entity endpoint with inclusion.
4 breadcrumbs TBD Not supported out of the box. May need to use main menu to build breadcrumbs by Frontend Website.
5 content /api/v1/page/<UUID>?include=field_page_paragraph Can be combined with 6
6 related content /api/v1/page/<UUID>?include=field_related_content Can be combined with 5
7 share block /api/v1/block_content/share_block Content is a static HTML
8 was this page helpful TBD Posting of webform submissions is not supported. Custom module may be required.
9 footer main menu /api/v1/menu_link_content/menu_link_content?filter[menu_name][value]=main
10 copyright /api/v1/block_content/copyright Content is a static HTML
11 footer menu /api/v1/menu_link_content/menu_link_content?filter[menu_name][value]=footer
12 Sites information /api/v1/taxonomy_term/sites Allows to retrieve custom site information stored in the Content Repository
13 Router /api/v1/router Alias lookup. May be required for ad-hoc queries from Frontend Website.

Request flow

The diagram below demonstrates how a single page is assembled by VueJS/Nuxt when hitting multiple endpoints.

VueJS/Nuxt retrieves site information and caches it internally until the next cache clear. Each Frontend Website has only site UUID (taxonomy term UUID from Site vocabulary) hardcoded. The rest of configuration comes from taxonomy term: logo, slogan, footer text, main menu name, footer menu name. This information is later used in follow-up requests to retrieve relevant information.

Note

Since Frontend Websites information is stored using fieldsable taxonomy terms, it is possible to add more fields to capture site information in the Content Repository. The data stored in these fields will become automatically exposed through API.

See Sites and sections for more information about organising per-site information.

Content API Content, Content API Menu, and Content API Block are shown here as separated endpoints (they are indeed separate), but are handled by the same internal entity controller from JSONAPI Drupal module. The content is filtered by Content API Filter based on provided site and path query parameters. If no site or path is provided the request is considered invalid and an error response is returned. Content API Filter is a thin layer (e.g. request event listener) in front of all JSONAPI endpoints. For example, if the content exists, but is not assigned to a site or section, it is considered as non-existing content when accessed from this site.

Internal Router is a mechanism to lookup internal path by provided alias. There is a similar functionality with a Router Endpoint, but it resolves aliases without additional Drupal-to-Drupal request.

End-to-end URL resolution

The diagram below describes how URL requested by a web browser is resolved by Drupal and served by VueJS/Nuxt. It covers both front-end and back-end mechanisms.

To leverage JSONAPI module in Drupal, we are using API Router to resolve requested paths to a set of information about the route (UUID, content type, path, alias). This information then used by Client API path builder to assemble a path to then send a second request to the JSONAPI endpoint.

It is important to note that due to how content links can be provided within content, the path resolution within API Router should be able to find the best match from either path or alias provided. For example, paths about-us/about-usnode/123/node/123 should all resolve to UUID of About Us page. When matched entity found, API Router also checks if it has the specified site assigned and returns an error if this path is not available. Also, API Router has all lookups cached in Drupal's dynamic cache, which is tagged with cache tags (so that the cache for path is cleared when entity is updated).

Router API endpoint

To control content aliases from Drupal and resolve requests coming from Browser into Frontend Website, a special Router endpoint exists to perform a lookup on provided path. Because the provided path can be either internal Drupal path or an alias, the lookup searches through all existing aliases and internal paths. 

When non-existing alias is provided, the endpoint returns an error according to JSONAPI specification. 

Router API also allows to filter by site if site URL query parameter is provided. 

Referenced entities

JSONAPI supports including referenced entities in response by using include query parameter and a comma-separated list of entity reference fields. For example, /api/v1/page/<UUID>?include=field_page_paragraph.

Drupal caching

Response headers pass-through Drupal-generated cache tags. This means that reverse proxies can bind to Drupal cache tags and invalidate their caches as soon as Drupal caches become invalid.