Category: Development

How to create a custom endpoint using Drupal JSON:API

Author(s)

Kevin
Kevin
If you’re looking for a way to provide a custom collections like jsonapi/articles/featured or contextual data like a jsonapi/me. You’re in the good place.
In this article, I will not explain how to create a custom REST Resources.
If you’re not familiar with JSON:API, I recommend you watch the following short video. It will provide valuable context for the remainder of this blog post:
Here I will try to expose you a step-by-step guide which explains why and how you can create a custom JSON:API Resource that leverage JSON:API Core feature.

JSON:API in short

To be franc - I don't want to read the story. Bring me to the code.

The JSON:API module is becoming wildly popular as an out-of-the-box way to provide an API server.
Why? Because it implements the {json:api} specification.

It’s still a RESTful interface, but the specification just helps bring an open standard for how data should be represented and requests should be constructed.

oopsie…
oopsie…

JSON:API Resource vs REST Resources ?

The principal interest of codding a JSON:API Resource instead of a classic REST Resource is to leverage the same standardized {json:api} output in your custom resource than the output of Drupal JSON:API out-of-the-box collections endpoints.
Believe me, frontenders will prefer dealing with 1 specification than having to transform and specify customization for few unstandardized output.
“ Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ”

Martin Fowler

Another advantage of extending the JSON:API with your own collection is the Core ability that will be available out-of-the-box:
  • Sorting
  • Filtering
  • Paginate
  • Selection of fields
  • Inclusion of relation

Deep Dive in Code

First, you will need to install a contrib module drupal/jsonapi_resources.
composer require drupal/jsonapi_resources
Don’t worry, the module is trustable, tested and stable. Plus it has been made by mglaman, one of the core-contributor of JSON:API for Drupal.

Routing

Before starting your custom Query Builder, you will need to define you’re own new route.
Let's defines a route for a collection containing featured articles nodes.
# Defines a route for a collection containing featured articles nodes.
my_custom_resource.jsonapi_resources.featured_articles:
  # %jsonapi% is a placeholder for the JSON:API base path, which can be
  # configured in a site's services.yml file.
  path: '/%jsonapi%/articles/featured'
  defaults:
    # Every JSON:API resource route must declare a _jsonapi_resource. The
    # value can either be a class or a service ID. Unlike the _controller
    # route default, it is not possible to declare a method name to be called.
    _jsonapi_resource: Drupal\my_custom_resource\Resource\FeaturedArticlesResource
    _jsonapi_resource_types: ['node--article']
  requirements:
    _permission: 'access content'
In case you need adding options (eg. a node or user), the same features as for common routes/controllers are available.
# Defines a route for a collection containing related articles of given node.
  path: '/%jsonapi%/articles/{page}/related'
  defaults:
    _jsonapi_resource: Drupal\my_custom_resource\Resource\RelatedArticlesOfPageResource
    _jsonapi_resource_types: ['node--article']
  requirements:
    _permission: 'access content'
  options:
    parameters:
      agent:
        type: entity:node
See the Structure of routes for more informations.

The Resource

Processes a request for a collection containing featured articles Nodes.
<?php

namespace Drupal\my_custom_resource\Resource;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\jsonapi\ResourceResponse;
use Drupal\jsonapi_resources\Resource\EntityQueryResourceBase;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Processes a request for a collection containing featured articles nodes.
 */
class FeaturedArticlesResource extends EntityQueryResourceBase {

  /**
   * Process the resource request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response.
   */
  public function process(Request $request): ResourceResponse {
    $cacheability = new CacheableMetadata();

    /** @var \Drupal\Core\Entity\ContentEntityType $entity_type */
    $entity_type = $this->entityTypeManager->getDefinition('node');
    /** @var string $bundle_field */
    $bundle_field = $entity_type->getKey('bundle');
    /** @var string $status_field */
    $status_field = $entity_type->getKey('status');

    $entity_query = $this->getEntityQuery('node')
      ->condition($bundle_field, 'article')
      ->condition('promote', TRUE)
      ->condition($status_field, NodeInterface::PUBLISHED);

    $cacheability->addCacheContexts(['url']);

    $paginator = $this->getPaginatorForRequest($request);
    $paginator->applyToQuery($entity_query, $cacheability);

    $data = $this->loadResourceObjectDataFromEntityQuery($entity_query, $cacheability);

    $pagination_links = $paginator->getPaginationLinks($entity_query, $cacheability, TRUE);

    /** @var \Drupal\jsonapi\CacheableResourceResponse $response */
    $response = $this->createJsonapiResponse($data, $request, 200, [], $pagination_links);
    $response->addCacheableDependency($cacheability);

    return $response;
  }

}
“ Well done you did it ! ”

Shia LaBeouf

That’s pretty much all the hocus-pocus that you need to have a custom JSON:API Resource that leverage JSON:API Core feature.
Imagination Spongebob

Digging Deeper

In case you need to upcast the URL parameter by UUID, I suggest you to use the converter paramconverter.jsonapi.entity_uuid .
# Defines a route for a collection containing related articles of given node.
  path: '/%jsonapi%/articles/{page}/related'
  defaults:
    _jsonapi_resource: Drupal\my_custom_resource\Resource\RelatedArticlesOfPageResource
    _jsonapi_resource_types: ['node--article']
  requirements:
    _permission: 'access content'
  options:
    parameters:
      agent:
        type: entity:node
        # Upcast entity parameters by UUID.
        converter: 'paramconverter.jsonapi.entity_uuid'

Sources

For the most curious of you, here are some sources of additional information that inspired the creation of this article.
Gabe Sullice (20 September, 2019) A New Era for Drupal’s JSON:API.
Seen on: https://sullice.com/.../a-new-era-for-drupals-jsonapi
Ivo Kovačević (03 Jully, 2019). How to create custom JSON-API resource drupal 8.
Seen on https://stackoverflow.com/.../how-to-create-custom-...
JSON:API Resources Documentation.
Seen on https://drupal.org/project/commerce/issues/2938731
Dries Buytaert (11 February, 2019). Headless CMS: REST vs JSON:API vs GraphQL.
Seen on https://dri.es/headless-cms-rest-vs-jsonapi-vs-graphql
**JSON:API Search API Documentation.
**Seen on: https://drupal.org/project/jsonapi_search_api

Have a Drupal project? We can certainly help! Visit our page to learn more.

More articles

Author(s)

Akira
Akira

Making of : “La nuit des Losers”

lanuitdeslosers.ch, built fast: paper wireframes, a bold hand-drawn identity, then Claude Code to prototype and ship. A mouse-tracking hero animation, a bowling game made of sponsor logos, and a few bugs we’re keeping.
lanuitdeslosers.ch, built fast: paper wireframes, a bold hand-drawn identity, then Claude Code to prototype and ship. A mouse-tracking hero animation, a bowling game made of sponsor logos, and a few bugs we’re keeping.
Making of : “La nuit des Losers”

Author(s)

Pierre
Pierre

Dark mode: necessity or trend

Nous faisons le tri entre idées reçues, réalités techniques et bonnes pratiques de design.
Nous faisons le tri entre idées reçues, réalités techniques et bonnes pratiques de design.
Dark mode: necessity or trend

Author(s)

Yann
Pierre
Yann and Pierre

Experiment #2 – Surway, a social experiments game

Pour notre deuxième expérimentation, nous avons relevé le défi de sortir de notre zone de confort en développant Surway, un jeu vidéo jouable directement dans le navigateur, où les actions de chaque joueur restent inscrites pour tous ceux qui suivront.
Pour notre deuxième expérimentation, nous avons relevé le défi de sortir de notre zone de confort en développant Surway, un jeu vidéo jouable directement dans le navigateur, où les actions de chaque joueur restent inscrites pour tous ceux qui suivront.
Experiment #2 – Surway, a social experiments game