Laravel HTTP Client: Named Instances &amp; Middleware Stacks | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Laravel Contextual HTTP Clients: Per-Service Config, Retries, and Middleware Stacks        On this page       1. [  The Problem With Ad-Hoc HTTP Clients ](#the-problem-with-ad-hoc-http-clients)
2. [  Registering a Named Client in a Service Provider ](#registering-a-named-client-in-a-service-provider)
3. [  Injecting Named Clients Into Services ](#injecting-named-clients-into-services)
4. [  Testing Without Hitting the Network ](#testing-without-hitting-the-network)
5. [  Retry Policies Worth Knowing ](#retry-policies-worth-knowing)
6. [  Takeaways ](#takeaways)

  ![Laravel Contextual HTTP Clients: Per-Service Config, Retries, and Middleware Stacks](https://cdn.msaied.com/223/2706e56a293bb3b59f39b52956efac09.png)

  #laravel   #http-client   #service-container   #testing  

 Laravel Contextual HTTP Clients: Per-Service Config, Retries, and Middleware Stacks 
=====================================================================================

     17 Jun 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   The Problem With Ad-Hoc HTTP Clients  ](#the-problem-with-ad-hoc-http-clients)
2. [  02   Registering a Named Client in a Service Provider  ](#registering-a-named-client-in-a-service-provider)
3. [  03   Injecting Named Clients Into Services  ](#injecting-named-clients-into-services)
4. [  04   Testing Without Hitting the Network  ](#testing-without-hitting-the-network)
5. [  05   Retry Policies Worth Knowing  ](#retry-policies-worth-knowing)
6. [  06   Takeaways  ](#takeaways)

 The Problem With Ad-Hoc HTTP Clients
------------------------------------

Most Laravel codebases scatter HTTP configuration everywhere. One service sets a base URL, another configures a timeout, a third adds an auth header — and none of it is testable in isolation. Laravel's `Http` facade is powerful, but without structure it becomes a maintenance liability.

The solution is to register **named, fully-configured HTTP client instances** through the service container, so every collaborator receives a ready-to-use client with the correct base URL, headers, retry policy, and middleware already applied.

Registering a Named Client in a Service Provider
------------------------------------------------

`Http::buildClient()` returns a `PendingRequest`, which is itself a fluent builder. You can bind one per external service:

```php
// app/Providers/HttpClientsServiceProvider.php

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Http;

class HttpClientsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton('http.stripe', function () {
            return Http::baseUrl(config('services.stripe.base_url'))
                ->withToken(config('services.stripe.secret'))
                ->timeout(15)
                ->retry(3, 200, function (\Exception $e) {
                    return $e instanceof \Illuminate\Http\Client\ConnectionException;
                })
                ->withMiddleware($this->idempotencyMiddleware());
        });

        $this->app->singleton('http.sendgrid', function () {
            return Http::baseUrl('https://api.sendgrid.com/v3')
                ->withHeader('Authorization', 'Bearer ' . config('services.sendgrid.key'))
                ->acceptJson()
                ->timeout(10)
                ->retry(2, 500);
        });
    }

    private function idempotencyMiddleware(): callable
    {
        return function (callable $handler) {
            return function ($request, array $options) use ($handler) {
                $request = $request->withHeader(
                    'Idempotency-Key',
                    (string) str()->uuid()
                );
                return $handler($request, $options);
            };
        };
    }
}

```

The middleware closure follows the Guzzle handler stack contract — Laravel's `PendingRequest::withMiddleware()` accepts any PSR-compatible Guzzle middleware.

Injecting Named Clients Into Services
-------------------------------------

Use contextual binding or a typed wrapper to keep service classes clean:

```php
// app/Services/StripeClient.php

use Illuminate\Http\Client\PendingRequest;

final class StripeClient
{
    public function __construct(
        private readonly PendingRequest $client
    ) {}

    public function createPaymentIntent(int $amountCents, string $currency): array
    {
        return $this->client
            ->post('/payment_intents', [
                'amount'   => $amountCents,
                'currency' => $currency,
            ])
            ->throw()
            ->json();
    }
}

```

Bind it contextually so the container knows which `PendingRequest` to inject:

```php
// Inside HttpClientsServiceProvider::register()

$this->app->when(StripeClient::class)
    ->needs(PendingRequest::class)
    ->give(fn ($app) => $app->make('http.stripe'));

```

Now `StripeClient` has zero configuration knowledge. It just calls endpoints.

Testing Without Hitting the Network
-----------------------------------

Laravel's `Http::fake()` intercepts all outgoing requests regardless of how the client was built, so your named instances are fully fakeable:

```php
// tests/Feature/StripeClientTest.php

use Illuminate\Support\Facades\Http;
use App\Services\StripeClient;

it('creates a payment intent', function () {
    Http::fake([
        '*/payment_intents' => Http::response([
            'id'     => 'pi_test_123',
            'status' => 'requires_payment_method',
        ], 201),
    ]);

    $result = app(StripeClient::class)->createPaymentIntent(5000, 'usd');

    expect($result['id'])->toBe('pi_test_123');

    Http::assertSent(fn ($request) =>
        $request->url() === config('services.stripe.base_url') . '/payment_intents'
        && $request['amount'] === 5000
    );
});

```

Because the `PendingRequest` is resolved fresh per singleton scope, `Http::fake()` intercepts correctly. If you need per-test isolation, swap the singleton for a `bind` or reset the fake between tests.

### Retry Policies Worth Knowing

- `retry(3, 200)` — three attempts, 200 ms fixed delay.
- Pass a `throw` boolean as the fourth argument (`false`) to suppress the final exception and return the last response instead.
- The callback form lets you retry only on specific exception types or response status codes, avoiding retries on 4xx client errors.

Takeaways
---------

- Register one named `PendingRequest` singleton per external service in a dedicated service provider.
- Use contextual binding to inject the correct client into each service class without string-keyed `app()` calls.
- Guzzle middleware added via `withMiddleware()` is the right place for cross-cutting concerns like idempotency keys, request signing, or structured logging.
- `Http::fake()` works transparently with named instances — no extra test infrastructure needed.
- Prefer `->throw()` on the response chain over manual status checks; it raises `RequestException` with the full response attached.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-contextual-http-clients-per-service-config-retries-and-middleware-stacks&text=Laravel+Contextual+HTTP+Clients%3A+Per-Service+Config%2C+Retries%2C+and+Middleware+Stacks) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-contextual-http-clients-per-service-config-retries-and-middleware-stacks) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does Http::fake() intercept requests made through a named PendingRequest singleton?        Yes. Laravel's fake layer patches the underlying Guzzle handler factory globally, so any PendingRequest instance — regardless of how it was constructed — will be intercepted when Http::fake() is active in a test. 

      Q02  Should I use a singleton or a transient binding for named HTTP clients?        Singleton is fine for stateless configuration (base URL, headers, timeout). If your middleware mutates per-request state — such as generating a unique idempotency key — ensure the mutation happens inside the middleware closure at request time, not at binding time, so the singleton remains safe. 

      Q03  Can I add structured logging to every outgoing request without touching each service class?        Yes. Add a Guzzle middleware via withMiddleware() in the service provider. The middleware receives the request before it is sent and the response after, giving you a single place to log URL, duration, and status code for every call made through that named client. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues](https://cdn.msaied.com/225/fc3ad6c9188459b1f2fb165912fca5b3.png) laravel queues jobs 

### Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues

Go beyond basic dispatching: learn how Laravel's Bus::batch(), job chains, and rate-limited middleware compose...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 17 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/job-batching-chaining-and-rate-limited-middleware-in-laravel-queues-1) [ ![Laravel Octane + FrankenPHP: Persistent State, Shared Services, and Safe Bootstrapping](https://cdn.msaied.com/224/cc0aa09965b63e7311e93282849ada05.png) laravel octane frankenphp 

### Laravel Octane + FrankenPHP: Persistent State, Shared Services, and Safe Bootstrapping

Running Laravel under FrankenPHP's worker mode unlocks real throughput gains, but persistent state between req...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 17 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-octane-frankenphp-persistent-state-shared-services-and-safe-bootstrapping) [ ![Laravel Signed Routes and Temporary URLs: Secure Link Patterns Beyond the Basics](https://cdn.msaied.com/222/1bcb5bdf8f9b2490d0898cfa28479582.png) laravel security routing 

### Laravel Signed Routes and Temporary URLs: Secure Link Patterns Beyond the Basics

Signed routes are more than a password-reset trick. Learn how to build expressive, tamper-proof URL workflows...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 17 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-signed-routes-and-temporary-urls-secure-link-patterns-beyond-the-basics) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
