Sunday 17 March 2019

Custom REST Resource with GET and POST Method Drupal 8

REST Resource with GET & POST Method

In custom REST resource created in previous posts, Now in this post GET & POST both are implemented.
Create a file named NodeExamplesRestResource.php in firstcustommodule > src > Plugin > rest > resource.

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 is common in both, id is unique and it is machine name for our resource and label appears as a resource name in backend. 
uri_path will define unique access / endpoint of our resource and so we do not need to define it routing.yml.

Now Code for file
<?php
/**
 * @file
 * Contains \Drupal\firstcustommodule\Plugin\rest\resource\NodeExamplesRestResource
 *
 */
namespace Drupal\firstcustommodule\Plugin\rest\resource;

use Drupal\Core\Session\AccountProxyInterface;
use Drupal\node\Entity\Node;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "node_example_rest_resource",
 *   label = @Translation("Node Example Create/Update/view Rest Resource"),
 *   serialization_class = "Drupal\node\Entity\Node",
 *   uri_paths = {
 *     "canonical" = "/examples-node-rest-resource",
 *     "https://www.drupal.org/link-relations/create" = "/examples-node-rest-resource"
 *   }
 * )
 */
class NodeExamplesRestResource extends ResourceBase {

  /**
   * A current user instance.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Constructs a new ExampleGetRestResource 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('example_node_rest'),
      $container->get('current_user')
    );
  }

  /**
   * Responds to POST requests.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity object.
   *
   * @return \Drupal\rest\ModifiedResourceResponse
   *   The HTTP response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function post($node_data) {
    // 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 ($node_data->nid->value == '') {
     $node = Node::create([
       'type' => $node_data->type->target_id,
'title' => $node_data->title->value,
'body' => [
  'summary' => '',
  'value' => $node_data->body->value,
  'format' => 'full_html',
],
      ]);      
      $node->save();           
    }
    elseif ($node_data->nid->value != '' && is_int($node_data->nid->value)) 
    {
      $values = \Drupal::entityQuery('node')->condition('nid', $node_data->nid->value)->execute();
      $node_exists = !empty($values);
      if ($node_exists) {
        $node = Node::load($node_data->nid->value);
// Title field set 
$node->setTitle($node_data->title->value);
// Body can now be an array with a value and a format.
// If body field exists.
$body = [
  'value' => $node_data->body->value,
  'format' => 'basic_html',
];
$node->set('body', $body);
$node->save();
//return new ResourceResponse($node);
      }
      else {
        \Drupal::logger('example_node_rest_api')->error('Node nid '.$node_data->nid->value.' not exists' );
return new ResourceResponse(['Error'=>'Node nid not exists']);
      }       
     }
     else {  
       return new ResourceResponse(['Error'=>'Data not corrected']);
     }
     return new ResourceResponse($node);     
  }

 /**
   * Responds to GET requests.
   *
   * Returns a list of bundles for specified entity.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function get() {
    // 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();
    }
    $entities = \Drupal::entityTypeManager()
      ->getStorage('node')
      ->loadMultiple();
    foreach ($entities as $entity) {
      $result[$entity->id()] = $entity->title->value;
    }
    $response = new ResourceResponse($result);
    // In order to generate fresh result every time (without clearing 
    // the cache), you need to invalidate the cache.
    $response->addCacheableDependency($result);
    return $response;
  }
}
After saving above file, resource will start to appear in the resource list. Enable this resource with configuration as shown in screenshot. 

Save this configuration.








Hit the url: localhost/examples-node-rest-resource/?_format=json
and response for the above url which is resource GET request response.

Now see the POST request. Open the above url in REST Client. I have used chrome REST client. 




Resource Request URL: http://drupal8moduledevelopement.dd:8083/examples-node-rest-resource/?_format=json
Method: POST
Parameters:
  Headers: 
    Content-Type: application/json
    x-CSRF-Token: (copy the token from url: domainname/session/token OR domainname/rest/session/token)
authorization: Basic YWRtaW46YWRtaW4=
  Body:
    Body content type: Raw Input
    Format Json: 
{
  "type": [
    {
      "target_id": "article"
    }
  ],
  "title": [
    {
      "value": "Article created by REST API 3rd"
    }
  ],
  "body": [
    {
      "value": "Rest api node create demo 3rd"
    }
  ],
  "nid": [
    {
      "value": ""
    }
  ]
}

Output of above request:
{"nid":[{"value":4}],"uuid":[{"value":"45caea8e-4510-4107-92ce-83a5066f3203"}],"vid":[{"value":8}],"langcode":[{"value":"en"}],"type":[{"target_id":"article","target_type":"node_type","target_uuid":"c
fde2b7b-1839-4a83-a359-4b1416512055"}],"revision_timestamp":[{"value":"2019-03-17T12:57:26+00:00","format":"Y-m-d\\TH:i:sP"}],"revision_uid":[{"target_id":1,"target_type":"user","target_uuid":"52858798-09de-4fe5-81ae-048295f8f5cd","url":"\/user\/1"}],"revision_log":[],"status":[{"value":true}],"title":[{"value":"Article created by REST API "}],"uid":[{"target_id":1,"target_type":"user","target_uuid":"52858798-09de-4fe5-81ae-048295f8f5cd","url":"\/user\/1"}],"created":[{"value":"2019-03-17T12:57:26+00:00","format":"Y-m-d\\TH:i:sP"}],"changed":[{"value":"2019-03-17T12:57:26+00:00","format":"Y-m-d\\TH:i:sP"}],"promote":[{"value":true}],"sticky":[{"value":false}],"default_langcode":[{"value":true}],"revision_translation_affected":[{"value":true}],"path":[{"alias":null,"pid":null,"langcode":"en"}],"body":[{"value":"Rest api node create demo","format":"full_html","processed":"Rest api node create demo","summary":""}],"comment":[{"status":2,"cid":0,"last_comment_timestamp":0,"last_comment_name":null,"last_comment_uid":0,"comment_count":0}],"field_image":[],"field_tags":[]}


2 comments:

  1. Hi it looks like your response object is not cached at all now.
    The addCacheableDependency method needs a object that implements CacheableDependencyInterface otherwise it will set the response as uncachable by setting max-age to 0.

    If you apply the node_list cache tag it will cache the response object as long as no node is being updated.
    To do so you can add:

    $cache_metadata = new CacheableMetadata();
    $cache_metadata->setCacheTags(['node_list']);

    and then apply that object to your response object by:

    $response->addCacheableDependency($cache_metadata);

    ReplyDelete