Calling an external API from a Laravel app used to mean reaching for raw cURL functions or file_get_contents() with a manually built stream context — the Http facade (introduced in Laravel 7, built on top of Guzzle) is the current standard replacement: a fluent, testable API for the same job.
1. A basic GET request
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users', [
'page' => 2,
]);
$data = $response->json(); // decoded array
$status = $response->status(); // 200
$ok = $response->successful(); // true for 2xx
2. POST with a JSON body
$response = Http::post('https://api.example.com/orders', [
'product_id' => 42,
'quantity' => 3,
]);
Http::post() sends the second argument as a JSON body by default, with the Content-Type: application/json header set automatically — no manual json_encode() or header configuration needed for the common case.
3. Headers and authentication
$response = Http::withToken($apiToken)
->withHeaders(['Accept' => 'application/json'])
->get('https://api.example.com/account');
// Basic auth
$response = Http::withBasicAuth('username', 'password')->get(...);
4. Timeouts and retries
$response = Http::timeout(10)
->retry(3, 200) // retry up to 3 times, 200ms between attempts
->get('https://api.example.com/data');
Without an explicit timeout, a slow or hanging external API can tie up a PHP worker process for the platform's default timeout (often 30–60 seconds) — for any external call, setting an explicit, appropriately short timeout is worth doing deliberately rather than relying on the default.
5. Handling errors properly
$response = Http::get('https://api.example.com/data');
if ($response->failed()) {
Log::error('API call failed', ['status' => $response->status(), 'body' => $response->body()]);
// handle the failure — don't assume $response->json() is safe to use here
}
// Or, to throw an exception on a 4xx/5xx response:
$response = Http::get('https://api.example.com/data')->throw();
By default, a 4xx or 5xx response doesn't throw an exception — $response->successful()/->failed() need to be checked explicitly, or ->throw() called deliberately to convert an error response into a catchable RequestException. Code that assumes every response is successful and calls ->json() unconditionally will silently work with an empty or unexpected array on failure rather than erroring visibly.
6. Faking HTTP calls in tests
Http::fake([
'api.example.com/*' => Http::response(['status' => 'ok'], 200),
]);
This is one of the concrete advantages over raw cURL: Http::fake() intercepts any matching outgoing request during a test and returns a canned response instead, so tests covering code that calls external APIs don't make real network requests, don't depend on a third-party service being up, and run fast and deterministically.