Laravel HTTP Client: Consume external APIs easily, robustly, and handle exceptions

- Andrés Cruz - ES En español

Video thumbnail

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.

HTTP Requests in Laravel Using the Http Facade and Pool Usage - Parallel Requests

Video thumbnail

On various occasions, we need to make HTTP requests directly from Laravel. This is equivalent to the logic we typically execute on the client using JavaScript with libraries like Axios or the Fetch API, but processed entirely on the backend.

With the rise of Artificial Intelligence services and third-party APIs, this feature is extremely useful. It allows us to consume external services from our Laravel application, receive the response, and process the data according to the project's needs.

Laravel's HTTP client is exposed through a facade (Illuminate\Support\Facades\Http), making its usage straightforward and expressive. It supports standard HTTP methods like GET, POST, PUT, PATCH, and DELETE, while also allowing parameter sending or form simulation through methods like asForm().

use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.ejemplo.com/datos');

Concurrent Requests Using the Pool Method

When we need to execute multiple HTTP requests within the same endpoint or process, running them sequentially can severely compromise application performance.

If we execute requests one after another outside of a concurrency block, each request will wait for the previous one to finish. To solve this issue, Laravel provides the pool method, which executes requests asynchronously and in parallel.

use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn ($pool) => [
    $pool->get('https://api.ejemplo.com/usuarios'),
    $pool->get('https://api.ejemplo.com/productos'),
    $pool->get('https://api.ejemplo.com/pedidos'),
]);

Difference in Execution Times

The advantage of the pool method is directly reflected in the total response time:

  • Sequential Execution: If each of three requests takes 1 second to respond, the total execution time will be approximately 3 seconds.
  • Parallel Execution (Pool): By being sent simultaneously, the total time will be slightly higher than the request that takes the longest to respond (approximately 1 second in total).

Response Inspection and Verification Methods

Once the request is executed, the response object provides a set of useful methods to inspect the result and HTTP status codes:

  • $response->ok(): Determines if the status code is 200.
  • $response->successful(): Verifies if the request was successful (2xx status codes).
  • $response->failed(): Evaluates if a client or server error occurred (4xx or 5xx codes).
  • $response->status(): Returns the exact HTTP status code.
  • $response->json(): Converts the response body directly into an array or JSON.
  • $response->body(): Retrieves the response in plain text or HTML.
  • $response->headers(): Gets the response headers.

Interactive Testing with Tinker

We can quickly evaluate how the HTTP client works by using Laravel's interactive console (php artisan tinker):

$ php artisan tinker
// Request execution
$response = Illuminate\Support\Facades\Http::get('https://google.com');
// Get the HTML response body
$body = $response->body();
// Check the HTTP status code
$status = $response->status(); // Returns 200
// Request a non-existent route
$notFound = Illuminate\Support\Facades\Http::get('https://google.com/ruta-inexistente');
$status = $notFound->status(); // Returns 404

This tool allows us to immediately verify the structure of the returned data before integrating the logic into our controllers or services.

Next step, learn how to use pagination in Laravel.

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.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.