overtrue/http
overtrue/http provides a simple, expressive HTTP client for PHP, making GET/POST requests, JSON handling, headers, and response parsing easy. It works in standalone PHP and Laravel projects, offering a lightweight alternative to heavier HTTP clients.
overtrue/http is a thin, expressive wrapper around Guzzle. It does not ship its own transport — it composes guzzlehttp/guzzle and exposes a smaller, Laravel-Http-like surface. That single fact drives most of the assessment below: the value you get is ergonomics, and the cost is one more dependency between you and Guzzle.
Install it like any Composer package:
composer require overtrue/http
Before writing code, open the following in this order:
README.md — the canonical usage examples; short and the fastest path to a working request.src/ — typically a Client/Http entry class plus a Response wrapper. Confirm the exact class name and static methods, since the wrapper is small enough to read end-to-end in a few minutes.composer.json — check the pinned Guzzle major version; this determines which request options you can pass through.A first use case worth running — hit an echo endpoint and inspect the wrapped response:
use Overtrue\Http\Http; // confirm class from README
$response = Http::get('https://httpbin.org/get', [
'query' => ['foo' => 'bar'],
'timeout' => 10,
]);
$status = $response->getStatusCode();
$body = $response->getBody()->getContents();
$json = json_decode($body, true);
If a static call feels too magic for your codebase, instantiate the client directly and inject it — the wrapper is stateless enough to be treated as a normal service.
Instance client as a configured service. The wrapper shines when you need one pre-configured client reused across a service class (base URI, auth header, timeout, user agent). Bind it once in your container and inject it; do not scatter Http::get(...) calls with repeated headers across the codebase.
use Overtrue\Http\Client;
$client = new Client([
'base_uri' => 'https://api.example.com/',
'timeout' => 15,
'headers' => [
'Authorization' => 'Bearer '.config('services.api.token'),
'Accept' => 'application/json',
],
]);
$users = $client->get('users', ['query' => ['page' => 2]]);
Pass Guzzle options through. The wrapper forwards query, json, form_params, body, headers, auth, timeout, connect_timeout, verify, proxy, cookies, and allow_redirects directly to the underlying Guzzle request. This is your real API surface — treat the wrapper's own method list as sugar and Guzzle's docs as the reference.
Response wrapper for JSON APIs. For internal APIs, wrap the raw call in a small method that enforces status checks and returns decoded data, rather than leaking the PSR-7 response up the stack:
public function fetchUsers(): array
{
$response = $this->client->get('users');
if ($response->getStatusCode() >= 400) {
throw new RuntimeException('Upstream error: '.$response->getBody());
}
return json_decode((string) $response->getBody(), true);
}
Laravel integration. Because the package predates the built-in Illuminate\Support\Facades\Http (7.x+) and mirrors it closely, the sensible pattern in a modern Laravel app is either (a) skip the package entirely and use the framework facade, or (b) register the client as a singleton in a service provider and inject it where you need instance-level config:
$this->app->singleton(Client::class, fn () => new Client([
'base_uri' => config('services.upstream.base_uri'),
'timeout' => 10,
]));
Middleware / handler stack. If you need retries, logging, or tracing, reach for Guzzle's handler stack via the underlying client. The wrapper does not abstract this away, so you keep full control — but you also need to know where the wrapper exposes the Guzzle client.
Non-Laravel projects. This is the honest home turf for the package: small PHP services, CLI tools, legacy apps where you want Http::get(...) ergonomics without pulling in the full framework.
composer.json constraints allow your Guzzle major. If they don't, you're pinning an old Guzzle — usually a non-starter.Http facade covers everything this package does and is maintained with the framework. Adopt overtrue/http only for non-Laravel contexts or legacy codebases already using it.GuzzleHttp\Exception\ConnectException, RequestException, and ClientException on network/4xx-5xx failures depending on the http_errors option. Confirm what the wrapper sets by default — if http_errors is enabled, a 500 throws instead of returning a response. That surprise is the most common "why didn't my response come back" bug.getBody()->getContents() consumes the stream. If you echo it once for logging and again for parsing, the second read returns empty. Cast to (string) $response->getBody() to rewind, or read once and store the string.'verify' => false is tempting when debugging internal endpoints; keep it out of production config and fix the CA bundle instead.json vs form_params is a real trap. Using form_params for a JSON API silently sends application/x-www-form-urlencoded and upstream may respond with a confusing 415 or 400. When in doubt, set the Content-Type header alongside json.timeout and often connect_timeout in the client config — otherwise one hung upstream can pin a queue worker indefinitely.Pool, retries, streaming downloads) is reachable through the underlying client or by dropping the wrapper and using Guzzle directly. Given the package size, "drop the wrapper" is a legitimate refactor path if you find yourself fighting it.How can I help you explore Laravel packages today?