kozz/laravel-guzzle-provider
Integrates Guzzle into Laravel by registering a configured HTTP client in the service container. Publishable config, sensible defaults, and a simple facade make outbound API calls clean and testable across your app.
Install the package:
composer require kozz/laravel-guzzle-provider
Register the service provider. On Laravel 5.5+ auto-discovery should pick it up from composer.json; on older versions add it to config/app.php:
'providers' => [
Kozz\Laravel\Guzzle\GuzzleServiceProvider::class,
],
If you want the facade, add it to the aliases array:
'aliases' => [
'Guzzle' => Kozz\Laravel\Guzzle\Facades\Guzzle::class,
],
Publish the config file so you can tune defaults:
php artisan vendor:publish --provider="Kozz\Laravel\Guzzle\GuzzleServiceProvider"
This drops a config/guzzle.php with things like base_url, timeout, headers, and handler/middleware arrays.
First use case — a simple API call:
use GuzzleHttp\Client;
public function __construct(private Client $client) {}
public function index()
{
$response = $this->client->get('users');
return $response->getBody()->getContents();
}
Or via the facade: Guzzle::get('users')->json();
Where to look first: read the package README, then config/guzzle.php, then open GuzzleServiceProvider.php to confirm how the client is bound and what constructor options are passed. That binding tells you exactly what the facade and container return.
Container binding as the primary integration. The package's real value is binding GuzzleHttp\Client into Laravel's container with sane defaults from config. Type-hint the client in constructors and let Laravel resolve it. This keeps your HTTP layer injectable and testable.
Facade for quick calls. For controllers, one-off commands, or scripts, Guzzle::get(...), Guzzle::post(...), etc. delegate to the underlying client. It's convenient but binds you to the facade — prefer injection in services and repositories.
Centralize base URLs and headers in config. Put per-service clients in config/guzzle.php (base URI, auth headers, timeouts) so environments shift via .env without code changes:
'base_url' => env('API_BASE_URL', 'https://api.example.com'),
'timeout' => env('API_TIMEOUT', 10),
'headers' => ['Accept' => 'application/json'],
Wrap calls in dedicated service classes. Don't sprinkle Guzzle::get() everywhere; create e.g. BillingClient, CrmClient that receive the client and expose domain methods. This is where most day-to-day value comes from — the provider handles wiring, your classes handle semantics.
Middleware for cross-cutting concerns. Use the handler config to push Middleware::log(), retry middleware, or a token-auth middleware. This is more maintainable than per-call withOptions. Example pattern:
$stack = HandlerStack::create();
$stack->push(Middleware::retry($decider));
$stack->push(Middleware::log($logger, new MessageFormatter()));
Bind the result back into the container or configure it via the package's handler setting.
Async requests. Guzzle 5/6 both support promises: $client->getAsync(...)->then(...)->wait();. Useful for fan-out calls in queue jobs or reporting endpoints.
Multi-client setups. If you need more than one base URL, don't fight the single binding — register your own named singletons in an app service provider and reuse the same Guzzle options. The package's config is a good template.
Testing pattern. Inject the client and swap it in tests with a client built on MockHandler + HandlerStack. This is the idiomatic Guzzle approach and works cleanly with the DI-based pattern above.
Http facade. Http::fake(), Http::assertSent(), and Http::retry() do not apply here. You're working with raw Guzzle: use MockHandler, HandlerStack, and history middleware for test assertions. Mixing the two mental models is the #1 source of confusion.RequestInterface vs Request, exceptions option semantics, and some middleware names differ. Pin your Guzzle version in composer.json and read the package's Guzzle constraint before blaming the provider for a Class not found.'Guzzle' => ... to aliases is easy to forget; on fresh installs, missing the alias produces "Class 'Guzzle' not found." Prefer DI to skip this entirely.php artisan config:cache will bake env() calls into the cached config. If you use env() inside config/guzzle.php (recommended) this is fine; if you use it at call sites, you'll get null in production. Always route env reads through config.base_uri. Guzzle concatenates base_uri and the request URI per RFC 3986. https://api.example.com/v1 + users becomes https://api.example.com/users, silently dropping /v1. Always end base_uri with a slash (.../v1/) and pass relative paths (users, not /users).RequestException is thrown for 4xx/5xx by default; ConnectException for network failures. Wrap calls in try/catch or set 'http_errors' => false per request and inspect $response->getStatusCode() yourself.timeout applies per-request; add a connect_timeout too. For retries, don't use Middleware::retry blindly — guard against non-idempotent methods (POST) or you'll double-charge users.['debug' => true] to stream cURL verbose output to stdout; useful in artisan tinker or a queued job with a temporary Log::info. For structured logging, Middleware::log($logger, new MessageFormatter('{method} {uri} {code} {res_header_Content-Length}')) is the standard lever.$this->app->singleton(Client::class, fn () => ...)) or extend the facade with a custom accessor. If your needs outgrow the defaults, subclassing the service provider is cleaner than editing vendor code.How can I help you explore Laravel packages today?