yuriitatur/api-client

Boilerplate for building api clients

dev-master 2025-08-11 19:21 UTC

This package is auto-updated.

Last update: 2025-08-11 19:21:11 UTC


README

Quality Gate Status Coverage

Basic Api Client

A boilerplate to quickly start using your own api client.

Installation

composer require yuriitatur/api-client

Usage

  1. Configuration

     use YuriiTatur\ApiClient\Services\StackBuilder;
     use GuzzleHttp\Client;
     use YuriiTatur\ApiClient\BasicApiClient;
     use YuriiTatur\ApiClient\ApiClientBuilder;
     use YuriiTatur\ApiClient\Services\Retry\MaxCountRetryStrategy;
        
     $api = (new ApiClientBuilder)
         ->withLogger($logger)
         ->withBaseUri('https://3rd-part-api.com')
         ->withGuzzleOptions([ # default guzzle options
             'verify' => $isProduction,
             'auth' => ['my-user', 'my-password']
         ])
         ->withCustomStack(function (StackBuilder $stack) { # create stack via builder or return your own
             return $stack->withHandler(new CurlHandler())
                 ->withNoExceptionConverter()
                 ->withRetryStrategy(new MaxCountRetryStrategy(2))
                 ->build();
         })
         ->build();
    
  2. Making calls as usual
     /**@var $api \YuriiTatur\ApiClient\BasicApiClient */
     $api->request('GET', 'https://google.com', [
         'query' => [
             'foo' => 'bar'
         ],
     ]);
    

Rate limiting Middleware

To use rate limiting middleware, you have to install symfony/rate-limiter library. For more advanced usage, to atomically track requests, you may want to install symfony/lock package. First, you need to create a middlewares. You can create as many middlewares as you have different limit cases. For example, let's create 2 middlewares to handle /v1 and /v2 routes.

composer require symfony/rate-limiter symfony/lock
use YuriiTatur\ApiClient\Middleware\RateLimitMiddleware;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
use Symfony\Component\Lock\Store\InMemoryStore;
use Symfony\Component\Lock\LockFactory;

# for more at https://symfony.com/doc/current/rate_limiter.html
$factoryV1 = new RateLimiterFactory([
    'id' => 'rate-limits-for-v1-routes',
    'policy' => 'sliding_window',
    'limit' => 100, # requests
    'interval' => '1 minute', 
], new InMemoryStorage(), new LockFactory(new InMemoryStore()));

$factoryV2 = new RateLimiterFactory([
    'id' => 'rate-limits-for-v2-routes',
    'policy' => 'sliding_window',
    'limit' => 10000, # requests
    'interval' => '1 hour', 
], new InMemoryStorage(), new LockFactory(new InMemoryStore()));

$rateLimitV1 = new RateLimitMiddleware(
    'v1-routes', 
    $factory->create('v1-routes-limiter'),
    $logger,
    10 # this is a max waiting time to make a request reservation
 );
 
 $rateLimitV2 = new RateLimitMiddleware(
    'v2-routes', 
    $factory->create('v2-routes-limiter'),
    $logger,
 );
 
 $stack->push($rateLimitV1, 'v1-limit');
 $stack->push($rateLimitV2, 'v2-limit');

After registering those middlewares in our stack, we are ready to use it, simply pass rate_limiter option in your request.

$api->get('https://api.domain.com/v1/users', [
    'rate_limiter' => 'v1-routes',
]);

$api->get('https://api.domain.com/v2/users', [
    'rate_limiter' => 'v2-routes',
]);

You may think why we need to specify limiters explicitly? if it's v1 => v1-routes. There are thousands of different combinations of request options. Some apis rate limit by api keys, some by route prefixes, others by called entities. You'll have to write your own logic. Simply create a new middleware and register it in stack like so: Note that this is very simplified code.

class MatchRateLimiterMiddleware {
    public function __invoke(callable $next) {
        return function (RequestInterface $request, array $options) use ($next) {
            if (strpos($request->getRequestTarget(), '/v1/') !== false) {
                $options['rate_limiter'] = 'v1-routes';
            }
            
            if (strpos($request->getRequestTarget(), '/v2/') !== false) {
                $options['rate_limiter'] = 'v2-routes';
            }
            
            return $next($request, $options);
        };
    }
}

$stack->before('v1-limit', new MatchRateLimiterMiddleware, 'rate-limit-matcher');

Laravel usage

This package contains a laravel service provider, being registered via composer. First that you want to do is to publish configs.

php artisan vendor:publish --tag=api-clients-config --force

This will create a file in your config directory called api-clients.php that contains api clients configuration. It looks something like this

return [
    'clients' => [
        'my-api-client' => [ # name of the client
            'options' => [ # guzzle options
                'base_uri' => 'https://api.domain.com',
                'verify' => !env('APP_DEBUG'),
            ],
            'response_transformers' => [], # extra response transformers
            'exception_converter' => GenericExceptionConverter::class,
            'tap' => null, # a simple callable, that modifies your stack
            'retry' => [ # retry settings
                'retry_strategy' => MaxCountRetryStrategy::class,
                'delay_strategy' => ConstantDelayStrategy::class,
            ],
        ],
    ],
    'rate_limiters' => [
        'default' => [ # name of the rate limiter
            'storage' => CacheStorage::class, # storate of made requests
            'lock_store' => RedisStore::class, # atomic lock storage
            'policy' => 'sliding_window', # token_bucket, fixed_window
            'limit' => 1, # number of requests
            'interval' => '5 minutes', # time
            'rate' => [ # token bucket config
                'interval' => '2 seconds', # spawn time
                'amount' => 1, # token count
            ],
            'max_wait_time' => 5, # seconds to obtain a request reservation
        ]
    ],
];

All these clients are being registered, and available from DI:

app('api-client.my-api-client');

Testing

You can test usage of this api client the same way you test a regular guzzle client. Either use MockHandler in your stack or spin-up a mock server. More on that here

To run library tests, run

composer run test

License

This code is under MIT license, read more in the LICENSE file.