Laravel HTTP Client: Consume external APIs easily, robustly, and handle exceptions
Content Index
- What is the Laravel HTTP Client and Why Use It?
- Types of Requests
- Differences Between Guzzle and the Laravel HTTP Client
- When It’s Worth Using in Real Projects
- How to Make HTTP Requests in Laravel (GET, POST, PUT, DELETE)
- GET Requests with Parameters
- Sending Data with POST, PUT, and PATCH
- Consuming APIs that Return JSON, Text, or HTML
- Handling HTTP Responses in Laravel
- Obtain body, json and status code
- Checking Successful and Failed Responses
- Sending Headers, Tokens, and Authentication in External APIs
- Custom Headers
- Basic Authentication
- Bearer Token Authentication
- Error Handling and Exceptions with the Laravel HTTP Client
- Using try/catch and ConnectionException
- Throwing Exceptions Automatically
- Registering Errors in Production
- Timeouts, Retries, and Concurrent Requests
- Concurrent Requests with Http::pool
- HTTP Macros and Client Reusability
- Testing External APIs with the Laravel HTTP Client
- Preventing Real Calls in Tests
- Best Practices when Consuming APIs with Laravel
- Frequently Asked Questions about the Laravel HTTP Client
- Conclusion: When and How to Use the Laravel HTTP Client
The Laravel HTTP client is a built-in feature of the framework that allows you to make HTTP requests from your Laravel application. With this, we can connect to any HTTP-based system or API, or any service capable of receiving HTTP requests. I went from consuming APIs with Guzzle "raw" to using the native HTTP client, and the change in code readability, order, and maintainability was massive.
Laravel allows us to interact with external or internal APIs in a clean, expressive way that is very well integrated with the framework. Whether it is to consume JSON, send data to an external service, or handle errors in production, the HTTP client is designed for the real day-to-day of a project.
What is the Laravel HTTP Client and Why Use It?
The Laravel HTTP client is an abstraction layer built on top of Guzzle, included by default in the framework since Laravel 7. Technically, it still uses Guzzle under the hood, but with a much clearer syntax oriented toward productivity.
Types of Requests
These requests can be of the basic types supported by HTTP, such as GET, POST, PUT, or DELETE. The Laravel HTTP client provides an easy-to-use and intuitive channel for making these types of requests.
Differences Between Guzzle and the Laravel HTTP Client
When you use Guzzle directly, you have total power, but also a more verbose syntax closer to "raw" PHP. In Laravel projects, this usually translates into code that is less readable and harder to maintain.
In contrast, the Laravel HTTP client:
- Drastically reduces boilerplate
- Chains methods expressively
- Integrates with testing, logging, and exceptions
- Facilitates retries, timeouts, and authentication
Direct Guzzle is only worth it in very specific cases. For 90% of API integrations, the Laravel HTTP client is the best choice.
When It’s Worth Using in Real Projects
- Consuming external REST APIs
- Integrating microservices
- Connecting with payment gateways, CRMs, or ERPs
- Testing endpoints without real calls
- Centralizing external integrations cleanly
How to Make HTTP Requests in Laravel (GET, POST, PUT, DELETE)
Let's learn how we can send HTTP requests in Laravel. These requests correspond to CRUD operations using the GET, POST, PUT, PATCH, and DELETE methods. With them, it is possible to communicate with any other system that allows receiving these types of requests to interconnect applications.
It doesn't matter if it's your own system or an external one: we can always send requests and work with the data we receive, whether it's JSON, text, an HTML page, an image, videos... etc.
Laravel supports all standard HTTP methods for CRUD operations.
With the guzzlehttp/guzzle package that has been installed with Laravel for a long time, you can see an example of the code for how we can perform these types of requests with this package:
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/user', [
'auth' => ['user', 'pass']
]);
echo $res->getStatusCode();
// "200"
echo $res->getHeader('content-type')[0];
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'
// Send an asynchronous request.
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
$promise = $client->sendAsync($request)->then(function ($response) {
echo 'I completed! ' . $response->getBody();
});
$promise->wait();We can do practically anything; the problem —if there really is a problem— is that the syntax isn't entirely clean. It reminds us a lot of pure PHP, and those of us who use Laravel like to have things more organized, with better readability, scalability, and, damn it, readable without so much hassle.
For that, Laravel now incorporates a native package integrated by default into the framework, whereas the previous one was simply a package installed automatically. You can see this package directly in your composer.json file:
Anyway, using the new package is much nicer *-*
$response = Http::get('https://google.com');
$response->body() : string;
$response->json() : array|mixed;GET Requests with Parameters
The classic one, let's start by making a GET request:
use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users', [
'page' => 1,
'limit' => 10,
]);
$data = $response->json();Here you can consume an external API and work directly with the resulting array. In real projects, this greatly simplifies controller and service logic.
Sending Data with POST, PUT, and PATCH
It doesn't matter if you make a GET or POST request..., you just have to change the method to the one you want to use
$response = Http::post('https://jsonplaceholder.typicode.com/posts', [
'title' => 'foo',
'body' => 'bar',
'userId' => 1,
]);
$result = $response->json();Changing from GET to POST or PUT is as simple as changing the method. That consistency is one of the great advantages of the HTTP client.
Consuming APIs that Return JSON, Text, or HTML
Not all APIs return JSON. Sometimes you will receive HTML, plain text, or even binaries.
$response = Http::get('https://google.com');
$html = $response->body();
***
Http::post('https://jsonplaceholder.typicode.com/posts',[
'title' => 'foo 2',
'body' => 'bar',
'userId' => 1,
])->json()In one project, I even ended up processing remote HTML to transform it internally, and the HTTP client supports it without any problems.
Handling HTTP Responses in Laravel
One of the strong points is how Laravel handles responses.
Obtain body, json and status code
$status = $response->status();
$body = $response->body();
$json = $response->json();You can capture the response and handle it however you want within the application. You can use plugins like CKEditor to view the HTML content you get when making the connection.
Checking Successful and Failed Responses
if ($response->successful()) {
// Código 2xx
}
if ($response->failed()) {
// Código distinto de 2xx
}Sending Headers, Tokens, and Authentication in External APIs
This point comes up constantly in real problems, as seen on Reddit.
Custom Headers
$response = Http::withHeaders([
'Accept' => 'application/json',
'X-API-KEY' => 'tu-api-key',
])->get('https://api.example.com/data');Basic Authentication
$response = Http::withBasicAuth('usuario', 'password')
->get('https://api.example.com/users');Bearer Token Authentication
$response = Http::withToken('tu-token')
->get('https://api.example.com/users');In more than one real integration, this was exactly the solution after testing with Postman and not knowing how to transfer it to Laravel.
Error Handling and Exceptions with the Laravel HTTP Client
In production, errors happen, and you have to handle them well.
Using try/catch and ConnectionException
use Illuminate\Http\Client\ConnectionException;
try {
$response = Http::get('https://api.example.com/users');
} catch (ConnectionException $e) {
report($e);
}It is always a good idea to handle potential exceptions that may occur when making connections.
The simplest way is through try-catch, where you can place the specific exception occurring or a generic one; these exceptions can happen when you use a route that doesn't exist or when the server is down...
Throwing Exceptions Automatically
$response = Http::get('https://api.example.com/users')->throw();This throws an exception if a 4xx or 5xx error occurs, something I personally use a lot when I want to fail fast.
Registering Errors in Production
Using report($e) allows leaving clear traces in the logs, something critical when an external API starts failing without warning.
You can register the potential exception in a Laravel log with report($e), and this is quite useful in production; for that, we use the function and pass it the exception.
Timeouts, Retries, and Concurrent Requests
Configuring timeout and retry
$response = Http::timeout(3)
->retry(3, 100)
->get('https://api.example.com/users');Perfect for unstable APIs.
Concurrent Requests with Http::pool
$responses = Http::pool(fn ($pool) => [
$pool->get('https://api.example.com/users'),
$pool->get('https://api.example.com/posts'),
]);Ideal when you need multiple resources without penalizing performance.
HTTP Macros and Client Reusability
Macros allow centralizing configurations of external APIs.
Http::macro('github', function () {
return Http::withToken(config('services.github.token'))
->baseUrl('https://api.github.com');
});
$response = Http::github()->get('/users/laravel');In large projects, this reduces duplication and human errors.
Testing External APIs with the Laravel HTTP Client
Laravel shines especially in testing.
Simulating responses with Http::fake
Http::fake([
'github.com/*' => Http::response(['name' => 'Taylor'], 200),
]);
$response = Http::get('https://github.com/users/taylor');Preventing Real Calls in Tests
Http::preventStrayRequests();This ensures that no test makes real requests by mistake.
Best Practices when Consuming APIs with Laravel
- Centralize integrations in services
- Never hardcode tokens or URLs
- Always use timeouts and retries
- Register errors in production
- Test without touching the network
These practices make the difference between an integration that "just works" and one that is robust and maintainable.
Frequently Asked Questions about the Laravel HTTP Client
- Does Laravel use Guzzle internally?
- Yes, the HTTP client is an abstraction over Guzzle.
- Is it better than using cURL directly?
- In Laravel, without a doubt. It is cleaner, testable, and more maintainable.
- Can it be used in production without issues?
- Yes, it is designed exactly for that.
- What happens if an external API fails?
- You can handle errors, retries, and exceptions in a controlled way.
Conclusion: When and How to Use the Laravel HTTP Client
The Laravel HTTP client is one of the best decisions of the framework for working with APIs. In my experience, it advantageously replaces direct Guzzle, improves code quality, and reduces errors in production.
If you consume external APIs regularly, learning it well is almost mandatory.
So, that is how easily you can use or send HTTP requests using Laravel with the new scheme we have internal to the framework; as you can see, it is much simpler and cleaner than the scheme we had previously. With this, you can interconnect systems and evaluate responses whether they are JSON, text, or something else.
Next step, learn how to use pagination in Laravel.
I agree to receive announcements of interest about this Blog.
We are going to explain how to make the first connection making HTTP requests from Laravel to other APIs or websites; which is a MUST for communicating applications from the server side.