mj4444/simple-http-client
A lightweight, easy-to-use HTTP client for Laravel and PHP applications. mj4444/simple-http-client provides a straightforward way to make outgoing requests and handle responses, keeping common API calls simple and readable in your code. It aims to reduce boilerplate for common HTTP calls.
This is an unvetted package (0 stars, 0 dependents, Unlicense). Treat the first 10 minutes as a code review, not a setup.
Read the package before installing it. It is almost certainly a handful of files:
composer require mj4444/simple-http-client
ls -R vendor/mj4444/simple-http-client/src
cat vendor/mj4444/simple-http-client/composer.json
From composer.json, confirm three things: the PSR-4 namespace, whether it declares extra.laravel.providers (auto-discovery) or needs manual registration, and which HTTP transport it depends on — Guzzle directly, or illuminate/http. That single fact determines every pattern below.
Look first at the client class constructor. The constructor signature is the whole API surface: does it take a base URI, a Guzzle ClientInterface, a config array, or nothing? Note whether it returns a PSR-7 ResponseInterface, a decoded array, or throws on 4xx/5xx. You need this before you write a single call.
Smoke-test in Tinker before wiring anything up.
$c = app(\Mj4444\SimpleHttpClient\Client::class);
dump($c->get('https://httpbin.org/get'));
If binding fails, it is not auto-discovered — register it manually in AppServiceProvider::register() or add the provider to bootstrap/providers.php.
First real use case: one read-only GET against a third-party JSON API, wrapped in a small adapter class of your own (see below). Do not call the package directly from controllers, jobs, or commands — the whole point of the adapter is that you can rip the package out in an afternoon.
If step 1 shows a package that is little more than a file_get_contents wrapper, or that hardcodes curl_* calls, stop and use Laravel's Http facade instead — you lose nothing and gain Http::fake(), retries, and a maintained dependency.
Bind behind your own interface. The package name advertises simplicity, which usually means a thin, non-final class with no interface of its own. Create one:
interface RatesApi { public function latest(string $base): array; }
final class SimpleClientRatesApi implements RatesApi
{
public function __construct(private \Mj4444\SimpleHttpClient\Client $client) {}
public function latest(string $base): array
{
$response = $this->client->get("https://api.example.com/latest?base={$base}");
return json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
}
}
Bind it in a provider, inject RatesApi everywhere, and the package name never appears outside that one file.
One adapter per upstream service. Do not build a generic ApiService that takes a URL argument — you get a God object with an untested HTTP client at its core. Adapters also give you a natural place to normalise the package's return type into your own DTOs.
Configure, don't hardcode. Put base URLs and timeouts in config/services.php and pass them into the client at construction. If the package has no config file of its own, publish nothing — just build the client in your provider:
$this->app->singleton(Client::class, fn () => new Client(
baseUri: config('services.rates.base_url'),
timeout: config('services.rates.timeout', 5),
));
Adjust to the real constructor; the shape is what matters.
Push calls to the queue. Any outbound HTTP call is a latency and failure source. Wrap adapter calls in a job with $tries, backoff(), and failed() handling rather than making a request inline in a web request.
Add resilience at the adapter, not in the package. If the package exposes no retry mechanism (likely), implement it in the adapter or a decorator:
final class RetryingRatesApi implements RatesApi
{
public function __construct(private RatesApi $inner) {}
public function latest(string $base): array
{
return retry(3, fn () => $this->inner->latest($base), 200, fn ($e) => true);
}
}
Decorate the interface binding, not the concrete client — no forking required.
Testing. If the package wraps Laravel's Http, Http::fake() covers you for free. If it wraps Guzzle, catch the client in the container: build a MockHandler + HandlerStack and bind a Guzzle client returning canned responses. Either way, fake at the interface boundary in feature tests and reserve transport-level fakes for the adapter's own unit test.
"mj4444/simple-http-client": "1.0.0", not ^1.0), commit composer.lock, and read the changelog before any update. If the source is under a few hundred lines, consider vendoring it into app/Support/ and dropping the dependency — the maintenance cost is near zero and you gain full control.guzzlehttp/guzzle; if this package requires an older major, Composer will either refuse to install or silently downgrade Guzzle and break something else. Run composer why guzzlehttp/guzzle after install and check composer update --dry-run output before merging.json_decode yields null and you get a confusing TypeError three layers up. Write one test that asserts behaviour on 404, 500, and a non-JSON body, and add an explicit $response->getStatusCode() >= 400 guard in your adapter if the package doesn't throw.curl_* clients frequently set none, and PHP's default default_socket_timeout of 60s will pin a queue worker for a minute per call. Always pass your own timeout at the adapter level.SECURITY.md. For anything touching credentials or PII, prefer Illuminate\Support\Facades\Http, which gives you the same simplicity with a maintained supply chain.extra.laravel absent will fail app(Client::class) with "Target is not instantiable" — the fix is registering it yourself, not debugging the container.tap() method. The reliable extension point is the decorator pattern around your own interface — design for that from the start and the package's internal limitations stop mattering.Bottom line: usable as an implementation detail behind one adapter, not as infrastructure. Before adopting, spend the ten minutes in vendor/ to confirm it does what the name implies; if it doesn't clearly beat Http::get()->throw()->json(), take the facade.
How can I help you explore Laravel packages today?