nahid/apiz
Nahid Apiz is a Laravel API package that speeds up REST API development with built-in versioning, response formatting, and resourceful controllers, so you can scaffold endpoints fast and keep consistent, clean JSON responses across your app.
Install via Composer:
composer require nahid/apiz
APIZ is a thin, opinionated wrapper around Guzzle, so the fastest path to understanding it is to read Nahid\Apiz\Apiz (the client) and Nahid\Apiz\Resource (the endpoint abstraction) in vendor/nahid/apiz/src. If you're on Laravel, the service provider and Apiz facade are auto-discovered; on standalone PHP, instantiate the client directly.
Publish the config (Laravel):
php artisan vendor:publish --provider="Nahid\Apiz\Providers\ApizServiceProvider" --tag="config"
Set a base_uri in config/apiz.php, then make your first call:
use Nahid\Apiz\Facades\Apiz;
$response = Apiz::get('/users');
$users = $response->json(); // decoded array
$status = $response->statusCode();
If you're not using Laravel, skip the facade and use the client object the same way. The first real use case is almost always: "I need a typed, reusable client for a third-party API instead of scattered Http:: / curl calls." Build one Resource class per API surface (users, invoices, webhooks) and inject the client.
One Resource class per domain. Resources inherit from Nahid\Apiz\Resource and expose named methods instead of raw URLs. This is the pattern the package is designed around.
namespace App\Http\Clients\Resources;
use Nahid\Apiz\Resource;
class UsersResource extends Resource
{
public function all(array $query = [])
{
return $this->get('/users', $query);
}
public function find(string $id)
{
return $this->get("/users/{$id}");
}
public function create(array $payload)
{
return $this->post('/users', $payload);
}
public function update(string $id, array $payload)
{
return $this->patch("/users/{$id}", $payload);
}
}
Register it once on a configured client and inject that client wherever needed:
$apiz = Apiz::make()
->withBaseUri(config('services.example.base_url'))
->withHeaders(['Accept' => 'application/json'])
->withAuth('Bearer', config('services.example.token'));
$users = $apiz->resource(UsersResource::class);
$users->find('42');
Compose per-request metadata fluently. Headers, query, body, and auth all chain:
$response = Apiz::withHeaders(['X-Request-Id' => (string) Str::uuid()])
->withQuery(['page' => 2, 'per_page' => 50])
->withBody(['name' => 'Jane'])
->post('/users');
Keep auth at the client level; pass per-request only the pieces that vary.
Map responses into DTOs at the resource boundary. Don't leak ->json() arrays into controllers. Wrap them:
public function find(string $id): UserData
{
return UserData::from($this->get("/users/{$id}")->json('data'));
}
public function all(): Collection
{
return collect($this->get('/users')->json('data'))
->map(fn ($row) => UserData::from($row));
}
Use a base Resource subclass to centralize retries, timeouts, logging, and default headers. This is the intended extension point — subclass Resource and point every concrete resource at it:
abstract class BaseResource extends Resource
{
protected function boot(): void
{
$this->withHeaders(['X-Client' => 'my-app'])
->withOptions(['timeout' => 10, 'connect_timeout' => 3]);
}
}
Testing. Swap the Guzzle handler for a MockHandler on the configured client so resource tests never touch the network:
$apiz->withHandler(HandlerStack::create(
new MockHandler([new Response(200, [], json_encode(['data' => ['id' => 1]]))])
));
$this->assertSame(1, $apiz->resource(UsersResource::class)->find('1')->id);
base_uri. /users and users behave differently depending on whether your base URI ends in a slash. Standardize on one convention (absolute paths + base URI ending in /) and write it down.->statusCode() explicitly or pass ['http_errors' => true] through withOptions() — but be consistent across resources.->body() and then ->json(), results may be surprising. Cache ->json() once and pass the array around.config('services.*') or env and inject via withAuth(). Avoid constructing a new authenticated client per request if you can share one.Pool), streamed downloads, cookies, or custom middleware stacks, reach for Guzzle directly or expose the underlying client. APIZ is for ergonomic typed access, not low-level control.debug option or attach a history middleware via withOptions/withHandler while diagnosing timeouts — APIZ's default error surface is intentionally thin.nahid/apiz, guzzlehttp/guzzle, and PHP versions in composer.json, and keep a thin internal wrapper (your BaseResource) around it so you can swap to raw Guzzle or Laravel's HTTP client later without rewriting every call site.config/app.php (or bootstrap/providers.php on Laravel 11+). Re-publish config after major upgrades.X-Request-Id header on every call and logging the response status + that ID from your resource layer. It turns "the API is slow" into a traceable event.How can I help you explore Laravel packages today?