Building Flexible REST API Clients in Drupal 8

Joel Steidl

Working with external APIs in Drupal has always been possible. Using PHP’s curl function or drupal_http_request is simple enough, but in Drupal 8 you can build in a lot more flexibility using Drupal::httpClient. Drupal::httpClient leverages Guzzle, a PHP HTTP client. If your project requires more than a single interaction with an external API a few steps can be taken to make those interactions much more reusable.

Make a Drupal Service for your API Connection

In Drupal 8, you can turn reusable functionality into a Service that you can easily reuse in your other custom code. Services can be called in module hooks and added to things like controllers using Dependency Injection. Defining a service in Drupal 8 requires a services.yml file in your custom module. For example, let's create a module called my_api:

The code from this example is based on a couple real-world API clients we’ve created recently.

  1. Watson API - IBM’s Watson API allows interacting with powerful utilities like speech to text. Rather than using Drupal’s httpClient in this case, we found a PHP class that already did the heavy lifting for us and we created a Drupal Service to extend that PHP class. In this case, we only needed to use Watson’s text-to-speech functionality to save text to audio files in Drupal, but ended up creating a flexible solution to interact with more than just the text-to-speech endpoint. You can check that code out on Drupal.org or GitHub.
  2. PCO API - Planning Center Online (PCO) is a CRM that is popular with faith-based institutions. PCO has a well documented RESTful API. In this case, I was building an application that needed to access the people stored in Planning Center in a Drupal 8 application. You can check that code out on Drupal.org or GitHub.
my_api.services.yml
services:
  my_api.client:
    class: '\Drupal\my_api\Client\MyClient'
    arguments: ['@http_client', '@key.repository', '@config.factory']

The class indication references the PHP class for your service; arguments references the other Services being utilized in the Class.

Now MyClient can be referenced throughout our code base. In a Drupal hook (example hook_cron) it might look something like this:

$client = Drupal::service('my_api.client');
$client->request();

In a Controller (example MyController.php) it might look like this:

<?php
 
namespace Drupal\my_custom_module\Controller;
 
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\my_api\MyClient;
 
/**
 * Class MyController.
 *
 * @package Drupal\my_custom_module\Controller
 */
class MyController extends ControllerBase {
 
  /**
   * Drupal\my_api\MyClient definition.
   *
   * @var \Drupal\my_api\MyClient
   */
  protected $myClient;
 
  /**
   * {@inheritdoc}
   */
  public function __construct(MyClient $my_client) {
    $this->myClient = $my_client;
  }
 
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('my_api.client')
    );
  }
 
  /**
   * Content.
   *
   * @return array
   *   Return array.
   */
  public function content() {
    $this->myClient->request());
    return [];
  }
}

Now we can use a common method in my_api.client like “request” to make calls to our external API. Connect then serves as a nice wrapper for the httpClient service.

public function request($method, $endpoint, $query, $body) {
  $response = $this->httpClient->{$method}(
    $this->base_uri . $endpoint,
    $this->buildOptions($query, $body)
  );
}

The request method accepts: A method (GET, POST, PATCH, DELETE, etc.) Endpoint (the API being used) Query (querystring parameters) Body

You can adjust your method parameters to best fit the API you are working with. In this example, the parameters defined covered the needed functionality.

$request = $this->myClient->request('post', 'people/v2/people', [], $body);

Using httpClient directly would look something like:

$response = $this->httpClient->post(
  'http://someapi.com/people/v2/people',
  [
    'auth' => ['token', 'secret'],
    'body' => json_encode($body),
  ]
);

Using httpClient instead of a service directly could litter hard coded values throughout your code base and insecurely expose authentication credentials.

Next time your project requires integrating with a 3rd party API, consider turning it into a service to set yourself up well for the future.


About the Author

With over ten years of professional experience, Joel's joint background in design, development and information architecture allows him to lead our development team with a focus on building user-centered web applications.

Read More