Create custom REST Resource for GET and POST method in Drupal 8

REST Resource Drupal 8

One of the important features that are inspired by drupal 7 and imported to Drupal 8 with enhancement is its REST API. Drupal provides inbuilt Rest support for exposing entities and creating new entities. You can view all those Rest endpoints by installing the rest_ui module. However, sometimes we also need to define custom Rest Resources / Endpoints for our custom requirement.

So here is how we can define custom Rest Resources.

I believe you all know how to develop custom drupal module, so let's start with understanding annotation for both GET and POST.

To enable the discovery of our Rest Resource by Rest System, we must declare annotation correctly and an example is below.

Annotation for GET:-

<?php
/**
 * @RestResource(
 *   id = "custom_rest_endpoint_get",
 *   label = @Translation("Endpoint GET"),
 *   uri_paths = {
 *     "canonical" = "/api/v1/article/{id}"
 *   }
 * )
 */

Annotation for POST:-

<?php
/**
 * @RestResource(
 *   id = "custom_rest_endpoint_post",
 *   label = @Translation("Endpoint POST"),
 *   serialization_class = "",
 *   uri_paths = {
 *     "https://www.drupal.org/link-relations/create" = "/api/v1/email",
 *   }
 * )
 */

Id and label are common in both, id will be unique or you can say it’s machine name for our resource and label appears as a resource name in the backend. uri_path will define unique access/endpoint of our resource and so we do not need to define it routing.yml.

Annotation for the post, we are not using any serialization class so we leave it empty.

Now here is the complete code of both. our namespace is Drupal\custom_rest\Plugin\rest\resource. it means our directory will be custom_reset/src/Plugin/rest/resource/

REST Resource class GET:-

<?php
namespace Drupal\custom_rest\Plugin\rest\resource;
use Drupal\node\Entity\Node;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Cache\CacheableResponseInterface;
/**
 * Annotation for get method
 *
 * @RestResource(
 *   id = "custom_rest_endpoint_get",
 *   label = @Translation("Endpoint GET"),
 *   uri_paths = {
 *     "canonical" = "/api/v1/article/{id}"
 *   }
 * )
 */
class ArticleSingleResources extends ResourceBase
{
    /**
     * A current user instance.
     *
     * @var \Drupal\Core\Session\AccountProxyInterface
     */
    protected $currentUser;
    /**
     * Constructs a Drupal\rest\Plugin\ResourceBase object.
     *
     * @param array $configuration
     *   A configuration array containing information about the plugin instance.
     * @param string $plugin_id
     *   The plugin_id for the plugin instance.
     * @param mixed $plugin_definition
     *   The plugin implementation definition.
     * @param array $serializer_formats
     *   The available serialization formats.
     * @param \Psr\Log\LoggerInterface $logger
     *   A logger instance.
     * @param \Drupal\Core\Session\AccountProxyInterface $current_user
     *   A current user instance.
     */
    public function __construct(
        array $configuration,
        $plugin_id,
        $plugin_definition,
        array $serializer_formats,
        LoggerInterface $logger,
        AccountProxyInterface $current_user)
    {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
$this->currentUser = $current_user;
    }
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition)
    {
        return new static(
            $configuration,
            $plugin_id,
            $plugin_definition,
            $container->getParameter('serializer.formats'),
            $container->get('logger.factory')->get('custom_rest'),
            $container->get('current_user')
        );
    }
    /**
     * Responds to GET requests.
     *
     * Returns a list of bundles for specified entity.
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     *   Throws exception expected.
     */
    public function get($node_id)
    {
        
        if($node_id){
            // Load node
            $node = Node::load($node_id);
if(is_object($node)){
$response_result[$node->id()] = $node->getTitle();
$response = new ResourceResponse($response_result);
                // Configure caching for results
                if ($response instanceof CacheableResponseInterface) {
                    $response->addCacheableDependency($response_result);
                }
                return $response;
            }
return new ResourceResponse('Article doesn\'t exist',400);
}
return new ResourceResponse('Article Id is required',400);
}
}

REST Resource class POST:-

<?php
namespace Drupal\custom_rest\Plugin\rest\resource;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
 * Annotaation for post method
 *
 * @RestResource(
 *   id = "custom_rest_endpoint_post",
 *   label = @Translation("Endpoint POST"),
 *   serialization_class = "",
 *   uri_paths = {
 *     "https://www.drupal.org/link-relations/create" = "/api/v1/email",
 *   }
 * )
 */
class EmailResources extends ResourceBase
{
    /**
     * A current user instance.
     *
     * @var \Drupal\Core\Session\AccountProxyInterface
     */
    protected $currentUser;
    /**
     * Constructs a Drupal\rest\Plugin\ResourceBase object.
     *
     * @param array $configuration
     *   A configuration array containing information about the plugin instance.
     * @param string $plugin_id
     *   The plugin_id for the plugin instance.
     * @param mixed $plugin_definition
     *   The plugin implementation definition.
     * @param array $serializer_formats
     *   The available serialization formats.
     * @param \Psr\Log\LoggerInterface $logger
     *   A logger instance.
     * @param \Drupal\Core\Session\AccountProxyInterface $current_user
     *   A current user instance.
     */
    public function __construct(
        array $configuration,
        $plugin_id,
        $plugin_definition,
        array $serializer_formats,
        LoggerInterface $logger,
        AccountProxyInterface $current_user)
    {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
$this->currentUser = $current_user;
    }
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition)
    {
        return new static(
            $configuration,
            $plugin_id,
            $plugin_definition,
            $container->getParameter('serializer.formats'),
            $container->get('logger.factory')->get('custom_rest'),
            $container->get('current_user')
        );
    }
    /**
     * Responds to POST requests.
     *
     * Returns a list of bundles for specified entity.
     *
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     *   Throws exception expected.
     */
    public function post($data)
    {
        $response_status['status'] = false;
        // You must to implement the logic of your REST Resource here.
        // Use current user after pass authentication to validate access.
        if (!$this->currentUser->hasPermission('access content')) {
            throw new AccessDeniedHttpException();
        }
if(!empty($data->email->value)){
            $system_site_config = \Drupal::config('system.site');
            $site_email = $system_site_config->get('mail');
$mailManager = \Drupal::service('plugin.manager.mail');
            $module = 'custom_rest';
            $key = 'notice';
            $to = $site_email;
            $params['message'] = $data->message->value;
            $params['title'] = $data->subject->value;
            $params['from'] = $data->email->value;
            $langcode = $data->lang->value;
            $send = true;
            $response_status['status'] = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send);
        }
$response = new ResourceResponse($response_status);
        return $response;
    }
}

Extra:-

Defining cacheable dependency class to make dealing with cacheability metadata (cache tags, cache contexts, and max-age)

<?php
namespace Drupal\custom_rest\Plugin\rest\resource;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\Cache;
class ArticleCacheableDepenency  implements CacheableDependencyInterface
{
// Number of results
    protected $result_count = 0;
// node array
    protected $node_array = [];
    /**
     * ArticleCacheableDepenency constructor.
     * @param $result_count
     * @param array $node_array
     */
    public function __construct($result_count,array $node_array) {
        $this->result_count = $result_count;
        $this->node_array = $node_array;
    }
    /**
     * {@inheritdoc}
     */
    public function getCacheContexts() {
        $contexts = [];
        // URL parameters as contexts
        if($this->result_count > 0 && !empty($this->node_array)) {
            $contexts[] = 'url.query_args';
        }
        return $contexts;
    }
    /**
     * {@inheritdoc}
     */
    public function getCacheTags() {
        $tags = [];
        foreach ($this->node_array as $key => $node_id){
            $tags[] = 'node:' . $node_id;
        }
        return $tags;
    }
    /**
     * {@inheritdoc}
     */
    public function getCacheMaxAge() {
        return Cache::PERMANENT;
    }
}

To use the above cacheable dependency class we need to make a few changes in our GET Rest Resource, and those changes are below.

<?php
// Change these lines
if ($response instanceof CacheableResponseInterface) {
    $response->addCacheableDependency($response_result);
}
// To
if ($response instanceof CacheableResponseInterface) {
    $response->addCacheableDependency(new ArticleCacheableDepenency(1,[1=>$node->id()]));
}