Using the HTTP Client of Laravel in packages

Laravel is shipped with an easy to use HTTP Client. This HTTP Client is based on the popular Guzzle HTTP Client but is a bit easier to use due of the expressive syntax. The HTTP Client is able to handle most common use cases but for more specific use cases you should use the Guzzle HTTP Client.

Knowing this, it might be interesting to use the HTTP Client in a package. Fortunately that possible!

Add the HTTP Client dependency

The HTTP Client needs to be installed in the package. Just run this composer command:

composer require illuminate/http

Using the HTTP client in the package

To provide useful examples, lets assume we want to build an API client for a JSON API which uses an API key. The API key should be used in every request.

In Laravel there’s a facade for the HTTP Client. In the package, this facade isn’t available.

The HTTP Client is initialized by the Illuminate\Http\Client\Factory. Your client should extend the factory.

use Illuminate\Http\Client\Factory;

class VdhictsApiClient extends Factory
{
  	private string $apiKey;
  
	public function __construct(string $apiKey, string $apiUrl)
    {
      	parent::__construct();
      
    	$this->apiKey = $apiKey;
      	$this->apiUrl = $apiUrl;
    }
}

With this setup, we already have access to the functionality of the HTTP Client:

$client = new VdhictsApiClient($apiKey, $apiUrl);
$client->get($url);

Even better is to provide methods for the endpoints. Let’s say we want to retrieve a list of items:

public function getItemList(): Response
{
    return $this
    	->acceptJson()
    	->withHeaders(['X-Api-Key' => $this->apiKey])
    	->get($this->apiUrl . '/items');
}

When a lot of endpoints are available, you could group those in traits which are used in the client.

Configure all requests

In the example above we needed to manually configure the request. For the example API, we need to include the API key, send and expect JSON. We also have a base url for all API endpoints. Also we want to provide a reasonable limit, to prevent long running requests when the API isn’t available. The last thing we want to do is provide a user agent for the package, so we are able to recognize where requests are coming from.

Summarized:

  • Include API key in every request
  • Use a specific user agent
  • Set the base url
  • Accept JSON
  • Send data as JSON

We don’t want to configure the request for every endpoint. It would be easier when we configure the request once and all API calls use that configuration.

This can be easily configured in the HTTP Client, but we want every request to have this configuration. That can be achieved by overwriting the newPendingRequest method:

protected function newPendingRequest(): PendingRequest
{
    return parent::newPendingRequest()
    	->withHeaders(['X-Api-Key' => $this->apiKey])
    	->withUserAgent('vdhicts/api-client-1.0')
    	->baseUrl($this->apiUrl)
    	->acceptJson()
    	->asJson();
}

Every request will use the API key, provides the user agent, has a base url, accepts JSON and sends the data as JSON.

Testing the API client

The HTTP Client offers several options for testing. The goal is to test if the request is properly configured.

First we need to add PHPUnit as dependency:

composer require --dev phpunit/phpunit

The next step is to create a test:

class VdhictsApiClientTest extends TestCase
{
    public function testRequest()
    {
      
    }
}

We should prevent the HTTP Client to send the requests to the API:

$client = new VdhictsApiClient('fake-api-key', 'fake-base-url');
$client->fake(); // this tells the client to not send the request

By using the fake method, the client will always assume a succesful response is received. You are able to provide custom responses by providing a parameter to the fake method, see Faking Responses at the Laravel docs for more information.

The next step is to check if the request is properly configured. The HTTP Client has the assertSent and assertNotSent assertions available for testing.

$response = $client->getItemList()
$this->assertTrue($response->ok());
$client->assertSent(function (Request $request) {
	return
		$request->hasHeader('X-Api-Key', 'fake-api-key') &&
		$request->hasHeader('Accept', 'application/json') &&
		$request->url() == 'fake-base-url/items';
});

Real-life example

Does this sounds interesting to you, but want to view a real-life example? I’ve used this in several packages myself which you can take a look at, for example: vdhicts/nuclino-api-client.