Hi guys,

I'm stuck on this weird issue on creating Rest APIs with Drupal 8.

The problem is that while I can make request to a GET endpoint and receive the correct response, the POST endpoint responds with an error message of "message": "No route found for \"POST /custom_practise_module/custom_api\": Method Not Allowed (Allow: GET, HEAD)"

Headers I passed with the requests includes Content-Type, X-CSRF-Token(got from rest/session/token), authorisation.
The REST resources have been enabled by REST UI for both the GET and POST methods.

The actual endponit is as following

  public function get() {
    $response = ['message' => 'Hello, this is a GET response!'];
    return  new ResourceResponse($response);
  }
  
   /**
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response objects
   */
  public function post() {

    $response = ['message' => 'Hello, this is a POST response!'];
    return  new ResourceResponse($response);
  }

I'm not sure where I got wrong and any help would be appreciated.

Thanks

Comments

even42’s picture

/**
 * Provides a Demo Resource
 *
 * @RestResource(
 *   id = "my_resource",
 *   label = @Translation("My Resource"),
 *   uri_paths = {
 *     "canonical" = "/api/my_ressource",
 *     "https://www.drupal.org/link-relations/create" = "/api/my_ressource/create"
 *   }
 * )
 */
class MyResource extends ResourceBase
{

  public function get() {
    $response = ['message' => 'Hello, this is a GET response!'];
    return  new ResourceResponse($response);
  }
  
   /**
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response objects
   */
  public function post() {

    $response = ['message' => 'Hello, this is a POST response!'];
    return  new ResourceResponse($response);
  }
}
Diamonds0a’s picture

I am having the exact same problem. What am I doing wrong? I cannot see it

<?php

namespace Drupal\pocket_catcher\Plugin\rest\resource;

use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;

/**
 * Provides a Demo Resource
 *
 * @RestResource(
 *   id = "redeem_code_resource",
 *   label = @Translation("Redeem Code Resource"),
 *   uri_paths = {
 *     "canonical" = "/pocket_catcher/redeem_code_resource",
 *     "https://www.drupal.org/link-relations/create" = "/pocket_catcher/redeem_code_resource/create"
 *   }
 * )
 */
class RedeemCodeResource extends ResourceBase {
    /**
     * Responds to entity POST requests.
     * @return \Drupal\rest\ResourceResponse
     */
    public function post(Request $request) {
        $response = ['message' => 'Hello, this is a rest service'];
        return new ResourceResponse($response);
    }

    public function get() {
        $response = ['message' => 'Hello, this is a rest service'];
        return new ResourceResponse($response);
    }
}
hallabol’s picture

I am facing same issue:

"message": "No route found for "POST /api/demo_resource": Method Not Allowed (Allow: GET, HEAD)"

rituraj.gupta’s picture

Please check if you have set the relation path like below:

uri_paths = {

* "canonical" = "/api/event_details/n_nid",

* "https://www.drupal.org/link-relations/create" = "/api/event_details/n_nid"

* }

----------
Warm Regards,
Ritu Raj

tyler.frankenstein’s picture

I hate to say it, but for me it ended up being something really simple. Back story... I created a custom Entity called Incident, and enabled the default REST resources that come with it (POST, GET, PATCH, DELETE). Which provides paths like /incident/1?_format=json, etc.

I then created a custom resource with GET and POST for a url of /incident/list, doing a GET worked fine on this, but doing the POST always ended up with the "no route found" error. Once I switch the canonical and link-relations paths to /list/incident, it worked immediately. So it looks like my first /incident/list path is/was somehow perhaps colliding with the POST /incident resource alongside the entity resource, instead of routing to my custom resource.

mani.ambal’s picture

i am not able to understand can you share your code example here?

gaspounet’s picture

Hi!

For those of you who care, I had the same problem after upgrading to Drupal 9, POST method for my REST resources didn't work anymore (404 error) when it still worked with Drupal 8.9.

I managed to fix the problem by replacing this line in the annotations of the resources :

"https://www.drupal.org/link-relations/create" = "/my_api/create_entity"

by this one :

"create" = "/my_api/create_entity"

Hope that this will help someone!

jorgespiessens’s picture

+1 This worked for me too!

rgry’s picture

Hi gaspounet

I have the same problem I think. Where exactly to you find that resource? I'm still kind of new to Drupal.

I even tried to make a case: https://www.drupal.org/project/drupal/issues/3153769 but no good answer as come...

gaspounet’s picture

Hi,

In order to build your custom REST API resource and to POST data, you first have to create a custom module and then create the following folder structure inside your module : "src/Plugin/rest/resource".

Inside this last folder you create your custom class (for example in a file "ResourceOfMyOwn.php" where you can put this kind of content (I return here a "ModifiedResourceResponse" object to send a JSON response):

<?php
namespace Drupal\my_custom_module\Plugin\rest\resource;
use Drupal\rest\ModifiedResourceResponse;
use Drupal\rest\Plugin\ResourceBase;
/**
 * @RestResource(
 *   id = "my_custom_resource",
 *   label = @Translation( "A custom REST API resource" ),
 *   uri_paths = {
 *     "canonical" = "/api/my_resource",
 *     "create" = "/api/my_resource"
 *   }
 * )
 */
class ResourceOfMyOwn extends ResourceBase
{
  public function post( $data )
  {
    $result_array = [];
    if ( $data[ 'a_field' ] )
    {
      //some stuff
    }
    //some other stuff
    return new ModifiedResourceResponse( [ "results" => $result_array ] );
  }
}

You need to clear the DRUPAL cache and go to /admin/config/services/rest in order to activate your resource.

Hope I understood and correctly answered your question.

rgry’s picture

Thanks for your answer. I think your example are more than I need?

I'm just trying to create instances of my content types. I't works in 8.9.3 but when I upgrade to 9.0.5 it give me no route found. Then I saw your post and thought that I my have the same problem?

Cangurin limpiezas’s picture

Hi!
i am only create one node via rest, not custom module

i have no problem in Drupal 8.9 but after upgrade to Drupal 9 i get:
"message": "No route found for \"POST /entity/node\""

any idea?,
Thanks

gaspounet’s picture

Weird, did you clear the cache after upgrade or did you enabled the "Content" ressource on the /admin/config/services/rest page?

Cangurin limpiezas’s picture

Hi gaspounet, thanks for response,.
Yes i clear cache and enabled the "Content" ressource on the /admin/config/services/rest

I can login, create and update profiles, get profiles ,get users, get node, but not create.
This issue describe similar problem.

Thanks.

briantes’s picture

Yes... you save me too!!!. I was getting mad with this issue. Thanks.

wahido’s picture

You saved My life

this my full worked example..:)

<?php
namespace Drupal\pg_rest_contact\Plugin\rest\resource;

use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\node\Entity\Node;
use Drupal\Core\Session\AccountProxyInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
 * Provides a Contact Resource
 *
 * @RestResource(
 *   id = "contact_resource",
 *   label = @Translation("Contact Resource"),
 *   uri_paths = {
 *     "canonical" = "/rest/api/post/contact-create",
 *     "create" = "/rest/api/post/contact-create"
 *   }
 * )
 */
class ContactResource extends ResourceBase {
  use StringTranslationTrait;
  /**
   * 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('rest_examples'),
      $container->get('current_user')
    );
  }
  /**
   * Responds to POST requests.
   *
   * Creates a new node.
   *
   * @param mixed $data
   *   Data to create the node.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function post($data) {

    // Use current user after pass authentication to validate access.
    if (!$this->currentUser->hasPermission('administer site content')) {

      // Display the default access denied page.
      throw new AccessDeniedHttpException('Access Denied.');
    }

    foreach ($data as $key => $value) {

      $node = Node::create(
        [
          'type' => $value['nodetype'],
          'title' => $value['title'],
          'body' => [
            'summary' => '',
            'value' => $value['body'],
            'format' => 'full_html',
          ],
        ]
      );
      $node->enforceIsNew();
      $node->save();

      $this->logger->notice($this->t("Node with nid @nid saved!\n", ['@nid' => $node->id()]));

      $nodes[] = $node->id();

    }

    $message = $this->t("New Nodes Created with nids : @message", ['@message' => implode(",", $nodes)]);

    return new ResourceResponse($message, 200);

  }
}
generalredneck’s picture

I wonder when this changed. sometime in the last year and half? Just found out on one my old custom modules that this was a problem and you helped! +1

XLD’s picture

Thank you!

esoo_tpxi’s picture

+1 This is still working in D9.3.6.

tripodcreative’s picture

I can confirm this fix is working as well. My full code example follows:

<?php

namespace Drupal\applesauce_endpoint\Plugin\rest\resource;

use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;


/**
 * Provides a resource to get view modes by entity and bundle.
 * @RestResource(
 *   id = "pumpkin_user_first_sign_on",
 *   label = @Translation("pumpkin User First Sign On"),
 *   uri_paths = {
 *     "canonical" = "/pumpkin-rest/user-first-signon",
 *     "create" = "/pumpkin-rest/user-first-signon"
 *   }
 * )
 */


 

 class pumpkinRestResource extends ResourceBase {
    
    protected $loggedUser;
    /**
     * Constructs a Drupal\rest\Plugin\ResourceBase object.
     *
     * @param array $config
     *   A configuration array which contains the information about the plugin instance.
     * @param string $module_id
     *   The module_id for the plugin instance.
     * @param mixed $module_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 currently logged user instance.
     */

     public function __construct(array $config, $module_id, $module_definition, array $serializer_formats, LoggerInterface $logger,AccountProxyInterface $current_user) {
        parent::__construct($config, $module_id, $module_definition, $serializer_formats, $logger);
        $this->loggedUser = $current_user;
      }

   /**
   * {@inheritdoc}
   */

   public static function create(ContainerInterface $container, array $config, $module_id, $module_definition) {
    return new static(
      $config,
      $module_id,
      $module_definition,
      $container->getParameter('serializer.formats'),
      $container->get('logger.factory')->get('applesauce_endpoint'),
      $container->get('current_user')
    );
  }

   /**
   * Responds to GET request.
   * Returns a list of taxonomy terms.
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   * Throws exception expected.
   */
  
  public function get() {
    // Implementing our custom REST Resource here.
    // Use currently logged user after passing authentication and validating the access of term list.
    if (!$this->loggedUser->hasPermission('access content')) {
      throw new AccessDeniedHttpException();
    }	
  //Apples vocabulary
	$vid = 'apples';
	$terms =\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
	foreach ($terms as $term) {
	  $term_result[] = array(
	    'id' => $term->tid,
		'name' => $term->name
	  );
	}

    $response = new ResourceResponse($term_result);
    //Uncomment if desired to cache
    //$response->addCacheableDependency($term_result);
    return $response;
  }
  

    /**
   * @return \Drupal\rest\ResourceResponse
   *   The HTTP response objects
   */
    public function post(Request $request) {
      //Set second param of TRUE to have data returned as array as opposed to stdclass object
       $data = json_decode($request->getContent(), TRUE);
       //Dump data to watchdog for testing
       \Drupal::logger("pumpkinrest")->debug("Got a request:<pre>". print_r($data, true) ."</pre>");
       return  new ResourceResponse($data);
      }
    


    //End parent class
 }
rituraj.gupta’s picture

Hello,

This is my code:

 * Represents Custom API records as resources.
 *
 * @RestResource (
 *   id = "custom_api",
 *   label = @Translation("Custom API"),
 *   uri_paths = {
 *     "canonical" = "/api/custom-api/{id}",
 *     "create" = "/api/custom-api"
 *   }
 * )

and still I am getting 404 in postman while hitting this url using GET method: https://mtn-drupal.ddev.site:8443/api/custom-api/22?_format=json. Permission is given also.

Only local images are allowed.

----------
Warm Regards,
Ritu Raj