Enhancing API Services with Laravel Saloon: A Brand Fetch Refactoring Example

itsimiro
5 min readMar 24, 2024

--

In the world of web development, efficient API services are the backbone of modern applications. They facilitate data communication between various components, ensuring seamless interactions and optimal performance. Laravel Saloon, a powerful toolkit in the Laravel ecosystem, offers developers a suite of tools and features to streamline API development and maintenance.

In this article, we’ll delve into the process of leveraging Laravel Saloon to refactor an API service, using a hypothetical scenario of “Brand Fetch” as our guiding example. By following this case study, developers can gain insights into how Laravel Saloon can enhance their workflow and elevate the performance of their API services.

Why is Saloon better?

Before opening this package, I had developed my own template for creating API services for integrations. It looked as follows:

class ApiClient
{
public function __construct(
private readonly Client $client,
private readonly UrlApiService $urlApiService
)
{}

public function retrieve(RetrieveBrandPayload $payload): array
{
$response = $this->sendRequest('GET', $this->urlApiService->retrieve($payload->getDomain()));
return $this->getResult($response);
}

/**
* @throws ApiException
*/
public function getResult(ResponseInterface $response): array
{
$statusCode = $response->getStatusCode();

if ($statusCode < 200 || $statusCode > 210) {
throw new ApiException("Invalid status code: $statusCode. Response: {$response->getBody()->getContents()}");
}

$result = json_decode($response->getBody()->getContents(), true);

if ($result === null) {
throw new ApiException('Invalid response.');
}

return $result;
}

public function sendRequest(string $method, string $url, array $bodyParams = [], array $queryParams = []): ResponseInterface
{
return $this->client->request($method, $this->urlApiService->getBuiltUrl($url), [
RequestOptions::HEADERS => $this->getHeaders(),
RequestOptions::JSON => $bodyParams,
RequestOptions::QUERY => $queryParams
]);
}

private function getHeaders(): array
{
return [
'Content-Type' => 'application/json',
'X-Token' => config('services.brand_fetch.token'),
];
}
}

it’s crucial to understand that each integration necessitates its own dedicated service. Considering the multitude of integrations, this entails a significant workload. While it’s plausible to construct our own abstraction layer to handle common tasks like headers and request transmission, this approach doesn’t alleviate the burden of creating distinct services for each integration. Moreover, if we aspire to incorporate advanced functionalities such as parallel request dispatching or batch requests, extending our abstraction layer becomes imperative.

How does it solve the problem?

In addressing your queries, Saloon emerges as a valuable solution. By organizing your API requests into reusable classes, Saloon enables you to centralize all your API configurations conveniently. Boasting an array of built-in features including test query recording, caching, OAuth2 integration, and pagination, Saloon provides a robust foundation for simplifying and standardizing API integrations within your application.

Refactoring with Laravel Saloon

Let’s delve into the steps involved in refactoring our hypothetical “Brand Fetch” API service using the features provided by Laravel Saloon

Installing the package:

To commence your journey with Saloon, installing it via Composer is the initial step.

composer require saloonphp/saloon "^3.0"

Creating a connector:

Creating a connector is a fundamental aspect of utilizing Saloon effectively. Connectors serve as classes that encapsulate key properties of an API integration, including its URL and headers. Moreover, any recurring behavior, such as authentication, should be encapsulated within a connector. It’s essential to maintain a separate connector for each distinct API integration to ensure modularity and maintainability.

To establish a standardized approach, it’s advisable to create a designated directory for storing API connectors within your project structure.

Below are the steps to create a connector and organize it within your project:

Define Connector Class:

  • Create a new PHP class for the connector, adhering to naming conventions that reflect the associated API integration (e.g., BrandFetchConnector for the BrandFetch API).
  • Within the connector class, define properties such as the API URL, request headers, and any authentication credentials or tokens required for accessing the API.

Here’s what I have:

class BrandFetchConnector extends Connector
{
public function resolveBaseUrl(): string
{
return config('integrations.connectors.brand_fetch.url');
}

protected function defaultAuth(): ?Authenticator
{
return new TokenAuthenticator(config('integrations.connectors.brand_fetch.token'));
}

protected function defaultHeaders(): array
{
return [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
];
}
}

Next, we need to create a Request:

In Saloon, each API request is represented by a class. Within these request classes, you specify essential details such as the endpoint, HTTP method, and associated properties like headers, query parameters, and request body.

class RetrieveBrandRequest extends Request
{
use HasConnector;

protected Method $method = Method::GET;

public function __construct(private readonly string $domain)
{}

protected function resolveConnector(): Connector
{
return new BrandFetchConnector();
}

public function createDtoFromResponse(Response $response): BrandDTO
{
return BrandFetchData::from($response->json())->toDTO();
}

public function resolveEndpoint(): string
{
return '/brands/' . $this->domain;
}
}

Saloon offers a highly beneficial feature enabling you to transform the response of your request into a Data Transfer Object (DTO). This feature proves invaluable as working with arrays can sometimes be unpredictable, whereas objects offer greater predictability and flexibility.

What happens next?

That’s it! Nothing more is required from you, now to send a brand request, you need to call the send method of the connector and pass the request there.

Here’s how I did it:

$this->brandFetchConnector->send(new RetrieveBrandRequest($domain))->dto();

This line of code sends a brand retrieval request using the BrandFetchConnector and the RetrieveBrandRequest, then transforms the response into a DTO.

When managing a multitude of requests, utilizing pools can greatly improve efficiency. Pools allow for simultaneous API calls to the same service while preserving an open CURL connection, resulting in substantial speed enhancements, particularly with a high volume of API calls. Saloon achieves parallelism by employing Guzzle’s underlying implementation, ensuring seamless execution.

Here’s another example from my project where I employed this feature to expedite text generation via OpenAI:

private function handleRequests(): void
{
$pool = $this->connector->pool(
requests: $this->requests,
concurrency: 10,
);

$pool->withResponseHandler(function (Response $response, string $key) {
[$hubId, $assetId] = explode(':', $key);

/** @var Hub $hub */
$hub = Hub::query()->find($hubId);

/** @var Asset $asset */
$asset = Asset::query()->find($assetId);

try {
$generator = $this->assetContentGeneratorFactory->make($asset->type);
$generator->generate($hub, $asset);
} catch (\Throwable) {
report('Failed to generate content for asset:'. $asset->getKey());
}
});

$pool->withExceptionHandler(function (FatalRequestException|RequestException $exception) {
report($exception);
});

$pool->send()->wait();
}

Saloon boasts a wide array of practical features awaiting discovery upon integration into your project, including middlewares, caching, plugins, rate limits, SDK, request retrying, and beyond.

Conclusion

In conclusion, leveraging Laravel Saloon empowers developers to streamline API development with efficiency and scalability in mind. By organizing API requests into reusable classes, centralizing configurations, and harnessing advanced features such as request transformation into DTOs and parallel request handling through pools, Saloon elevates the development process to new heights.

With Saloon, developers can create standardized and robust API integrations, enhancing performance, maintainability, and overall user experience. Whether it’s optimizing existing API services or embarking on new integrations, Saloon proves to be an indispensable tool in the modern developer’s toolkit.

As we navigate the ever-evolving landscape of web development, embracing tools like Saloon enables us to tackle challenges with confidence, delivering exceptional solutions and driving innovation forward. So, let’s harness the power of Laravel Saloon and embark on a journey of enhanced API development excellence.

Thanks for reading! I look forward to your feedback.

--

--

itsimiro

Passionate developer exploring the realms of software engineering. Writing about tech, coding adventures, and everything in between!