Calling an external API from PHP can be done with raw cURL, file_get_contents() with a stream context, or Laravel's own Http facade — all three ultimately do the same job, but in a Laravel application the Http facade is almost always the better choice.
Raw cURL: a GET request with headers
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.example.com/users',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer '.config('services.example.token'),
'Accept: application/json',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
Raw cURL: a POST request with a JSON body
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.example.com/orders',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['product_id' => 42, 'quantity' => 2]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer '.config('services.example.token'),
],
]);
$response = curl_exec($curl);
curl_close($curl);
file_get_contents with a stream context
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nAuthorization: Bearer ".config('services.example.token'),
'content' => json_encode(['product_id' => 42, 'quantity' => 2]),
],
]);
$response = file_get_contents('https://api.example.com/orders', false, $context);
$data = json_decode($response, true);
This works without the cURL PHP extension installed, but it offers considerably less control over timeouts, redirects, and error handling than cURL does, and silently returns false rather than throwing on failure — checking the return value explicitly matters more here than with either of the other two approaches.
Laravel's Http facade (the recommended approach in a Laravel app)
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.example.token'))
->get('https://api.example.com/users');
$data = $response->json();
$response = Http::withToken(config('services.example.token'))
->post('https://api.example.com/orders', [
'product_id' => 42,
'quantity' => 2,
]);
if ($response->successful()) {
$order = $response->json();
} elseif ($response->failed()) {
Log::error('Order API call failed', ['status' => $response->status()]);
}
The Http facade wraps Guzzle under a fluent, readable API — automatic JSON encoding/decoding, straightforward header and auth helpers (withToken(), withHeaders()), and response-status helper methods (successful(), failed(), status()) that raw cURL requires writing manually every time.
Setting a timeout and retrying on failure
$response = Http::timeout(10)
->retry(3, 100)
->post('https://api.example.com/orders', $payload);
retry(3, 100) retries the request up to 3 times with a 100ms delay between attempts — genuinely useful for a flaky third-party API, and something that would take considerably more manual code to implement correctly with raw cURL.
Why the Http facade is the practical default in Laravel
Beyond the cleaner syntax, the Http facade integrates with Laravel's HTTP client testing helpers (Http::fake()), letting you mock external API calls in tests without hitting the real network — something raw cURL or file_get_contents calls don't get for free.