You are here

JSON:API Architecture in Drupal 10

Same name and namespace in other branches
  1. 8 core/modules/jsonapi/jsonapi.api.php \jsonapi_architecture
  2. 9 core/modules/jsonapi/jsonapi.api.php \jsonapi_architecture

Overview

The JSON:API module is a Drupal-centric implementation of the JSON:API specification. By its own definition, the JSON:API specification "is a specification for how a client should request that resources be fetched or modified, and how a server should respond to those requests. [It] is designed to minimize both the number of requests and the amount of data transmitted between clients and servers. This efficiency is achieved without compromising readability, flexibility, or discoverability."

While "Drupal-centric", the JSON:API module is committed to strict compliance with the specification. Wherever possible, the module attempts to implement the specification in a way which is compatible and familiar with the patterns and concepts inherent to Drupal. However, when "Drupalisms" cannot be reconciled with the specification, the module will always choose the implementation most faithful to the specification.

Resources

Every unit of data in the specification is a "resource". The specification defines how a client should interact with a server to fetch and manipulate these resources.

The JSON:API module maps every entity type + bundle to a resource type. Since the specification does not have a concept of resource type inheritance or composition, the JSON:API module implements different bundles of the same entity type as *distinct* resource types.

While it is theoretically possible to expose arbitrary data as resources, the JSON:API module only exposes resources from (config and content) entities. This eliminates the need for another abstraction layer in order implement certain features of the specification.

Relationships

The specification defines semantics for the "relationships" between resources. Since the JSON:API module defines every entity type + bundle as a resource type and does not allow non-entity resources, it is able to use entity references to automatically define and represent the relationships between all resources.

Resource versioning

The JSON:API module exposes entity revisions in a manner inspired by RFC5829: Link Relation Types for Simple Version Navigation between Web Resources.

Revision support is not an official part of the JSON:API specification. However, a number of "profiles" are being developed (also not officially part in the spec, but already committed to JSON:API v1.1) to standardize any custom behaviors that the JSON:API module has developed (all of which are still specification-compliant).

By implementing revision support as a profile, the JSON:API module should be maximally compatible with other systems.

A "version" in the JSON:API module is any revision that was previously, or is currently, a default revision. Not all revisions are considered to be a "version". Revisions that are not marked as a "default" revision are considered "working copies" since they are not usually publicly available and are the revisions to which most new work is applied.

When the Content Moderation module is installed, it is possible that the most recent default revision is *not* the latest revision.

Requesting a resource version is done via a URL query parameter. It has the following form:


             version-identifier
                   __|__
                  /     \
?resourceVersion=foo:bar
                  \_/ \_/
                   |   |
   version-negotiator  |
               version-argument

A version identifier is a string with enough information to load a particular revision. The version negotiator component names the negotiation mechanism for loading a revision. Currently, this can be either `id` or `rel`. The `id` negotiator takes a version argument which is the desired revision ID. The `rel` negotiator takes a version argument which is either the string `latest-version` or the string `working-copy`.

In the future, other negotiatiors may be developed, such as negotiatiors that are UUID-, timestamp-, or workspace-based.

To illustrate how a particular entity revision is requested, imagine a node that has a "Published" revision and a subsequent "Draft" revision.

Using JSON:API, one could request the "Published" node by requesting `/jsonapi/node/page/{{uuid}}?resourceVersion=rel:latest-version`.

To preview an entity that is still a work-in-progress (i.e. the "Draft" revision) one could request `/jsonapi/node/page/{{uuid}}?resourceVersion=rel:working-copy`.

To request a specific revision ID, one can request `/jsonapi/node/page/{{uuid}}?resourceVersion=id:{{revision_id}}`.

It is not yet possible to request a collection of revisions. This is still under development in issue [#3009588].

Resource translations

Some multilingual features currently do not work well with JSON:API. See JSON:API modules's multilingual support documentation online for more information on the current status of multilingual support.

API

The JSON:API module provides an HTTP API that adheres to the JSON:API specification.

The JSON:API module provides *no PHP API to modify its behavior.* It is designed to have zero configuration.

  • Adding new resources/resource types is unsupported: all entities/entity types are exposed automatically. If you want to expose more data via the JSON:API module, the data must be defined as entity. See the "Resources" section.
  • Custom field type normalization is not supported because the JSON:API specification requires specific representations for resources (entities), attributes on resources (non-entity reference fields) and relationships between those resources (entity reference fields). A field contains properties, and properties are of a certain data type. All non-internal properties on a field are normalized.
  • The same data type normalizers as those used by core's Serialization and REST modules are also used by the JSON:API module.
  • All available authentication mechanisms are allowed.

Test Coverage

The JSON:API module comes with extensive unit and kernel tests. But most importantly for end users, it also has comprehensive integration tests. These integration tests are designed to:

  • ensure a great DX (Developer Experience)
  • detect regressions and normalization changes before shipping a release
  • guarantee 100% of Drupal core's entity types work as expected

The integration tests test the same common cases and edge cases using \Drupal\Tests\jsonapi\Functional\ResourceTestBase, which is a base class subclassed for every entity type that Drupal core ships with. It is ensured that 100% of Drupal core's entity types are tested thanks to \Drupal\Tests\jsonapi\Functional\TestCoverageTest.

Custom entity type developers can get the same assurances by subclassing it for their entity types.

Backwards Compatibility

PHP API: there is no PHP API except for three security-related hooks. This means that this module's implementation details are entirely free to change at any time.

Note that *normalizers are internal implementation details.* While normalizers are services, they are *not* to be used directly. This is due to the design of the Symfony Serialization component, not because the JSON:API module wanted to publicly expose services.

HTTP API: URLs and JSON response structures are considered part of this module's public API. However, inconsistencies with the JSON:API specification will be considered bugs. Fixes which bring the module into compliance with the specification are *not* guaranteed to be backwards-compatible. When compliance bugs are found, clients are expected to be made compatible with both the pre-fix and post-fix representations.

What this means for developing consumers of the HTTP API is that *clients should be implemented from the specification first and foremost.* This should mitigate implicit dependencies on implementation details or inconsistencies with the specification that are specific to this module.

To help develop compatible clients, every response indicates the version of the JSON:API specification used under its "jsonapi" key. Future releases *may* increment the minor version number if the module implements features of a later specification. Remember that the specification stipulates that future versions *will* remain backwards-compatible as only additions may be released.

Tests: subclasses of base test classes may contain BC breaks between minor releases, to allow minor releases to A) comply better with the JSON:API spec, B) guarantee that all resource types (and therefore entity types) function as expected, C) update to future versions of the JSON:API spec.

See also

http://jsonapi.org/

https://github.com/json-api/json-api/pull/1268

https://github.com/json-api/json-api/pull/1311

https://www.drupal.org/project/drupal/issues/2955020

https://www.drupal.org/project/drupal/issues/3009588.

https://tools.ietf.org/html/rfc5829

https://www.drupal.org/docs/8/modules/jsonapi/revisions

https://www.drupal.org/docs/8/modules/jsonapi/translations

http://jsonapi.org/faq/#what-is-the-meaning-of-json-apis-version

File

core/modules/jsonapi/jsonapi.api.php, line 10
Documentation related to JSON:API.