Laravel cookbook · Beginner
Call external APIs with timeouts, retries and fakes
Calls to a partner API hang, fail on temporary errors, and make tests depend on the network.
Approach
Configure the HTTP client with a base URL, token, timeouts and retries with backoff for connection errors and 5xx responses only. In tests, fake responses and assert what was sent.
Solution
<?php
// app/Services/ShippingClient.php
namespace App\Services;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
class ShippingClient
{
public function quote(string $country, int $weightGrams): int
{
return Http::baseUrl(config('services.shipping.url'))
->withToken(config('services.shipping.token'))
->acceptJson()
->connectTimeout(2)
->timeout(5)
->retry([200, 500, 1000], when: fn ($e) => $e instanceof ConnectionException
|| ($e instanceof RequestException && $e->response->serverError()))
->get('/quotes', ['country' => $country, 'weight' => $weightGrams])
->throw()
->json('price_cents');
}
}
<?php
// in a test
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
Http::fake([
'shipping.example.com/quotes*' => Http::sequence()->pushStatus(503)->push(['price_cents' => 990]),
]);
$price = app(\App\Services\ShippingClient::class)->quote('DE', 1200);
Http::assertSentCount(2);
Http::assertSent(fn (Request $request) => $request['country'] === 'DE');
Pitfalls
- No timeout: a slow partner ties up PHP workers until the server's limit.
- Retrying non-idempotent POSTs (payments) without an idempotency key.
- Retrying 4xx errors, which won't succeed on retry.
Variations
- Register a macro (
Http::macro('shipping', ...)) to reuse the configured client. - Call
Http::preventStrayRequests()in tests so unfaked requests fail.
Further reading: HTTP client
Reference: https://laravel.com/docs/13.x/http-client
More Laravel recipes
- Authorize actions with policies
- Cache expensive data with stale-while-revalidate
- Catch N+1 queries with strict Eloquent mode
- Delete old records automatically with prunable models
- Dispatch events and listeners after the transaction commits
- Issue scoped API tokens with Sanctum
- Make queued jobs reliable with retries, backoff and uniqueness
- Prevent race conditions with transactions and row locks