nazarii-kretovych/laravel-api-model-driver
An Eloquent driver for Laravel that backs models with external APIs instead of a database, so you can query remote HTTP resources while keeping familiar models, relationships, casts, and conventions, without rewriting your data layer.
This package targets Laravel 7 / PHP 7.2+ and is explicitly a read-oriented bridge: it lets an external REST resource behave like an Eloquent model so you can call familiar methods (find, where, get, relationships) against an API instead of a table. Treat that scope as the contract — it is not a full ORM replacement.
Install and register
composer require nazarii-kretovych/laravel-api-model-driver
Laravel 7 auto-discovers the service provider. If the repo ships a config file, publish it and set the defaults (base URL, timeout, auth) there rather than hard-coding per model:
php artisan vendor:publish --provider="NazariiKretovych\ApiModelDriver\..."
Define your first API model
Create a model that extends the package's base API model (check src/ and the README for the exact base class name — the namespace follows NazariiKretovych\ApiModelDriver). Configure only what the API actually exposes:
class ApiUser extends ApiModel
{
protected $primaryKey = 'id';
protected $fillable = ['id', 'name', 'email'];
protected $casts = ['created_at' => 'datetime'];
// point at the endpoint, and set auth if needed
protected $endpoint = 'users';
}
First use case: wrap a read-only third-party resource. The lowest-risk, highest-value entry point is a resource you only ever read — a CRM contacts endpoint, a remote users directory, a status service. You get ApiUser::find(1), ApiUser::where('role', 'admin')->get(), and $user->posts for free, without writing a bespoke client class for every endpoint.
Where to look first
src/ — this is where the HTTP call is actually made and where attribute mapping happens.Wire one model against a real sandbox endpoint before building anything on top of it.
Model-per-resource, thin subclasses. Keep API models thin: endpoint, fillable fields, casts, primary key, relationships. Put any transformation logic in accessors or in a dedicated resource/DTO layer so the model stays a faithful map of the remote payload.
Queries as query-string translation. where('status', 'active') typically becomes a ?status=active query param. Confirm how the driver maps operators (where('age', '>', 18), whereIn) against what the remote API supports — only use the subset the API understands, and fall back to client-side filtering with collect() for anything exotic.
Relationships across the boundary. The real payoff is mixing API models and DB models in one graph:
class ApiUser extends ApiModel
{
public function orders()
{
return $this->hasMany(Order::class, 'user_id'); // local DB
}
}
This works cleanly when the foreign key exists on the local side. The reverse — a DB model hasMany an API model — is where eager loading gets expensive; prefer resolving those lazily and caching.
Cache aggressively behind the model. An API model is a natural place for a read-through cache. Wrap find/get results in Cache::remember keyed by endpoint + query, with a short TTL. This is the single biggest day-to-day win: it turns chatty UI code into something with predictable latency.
Pagination. Expect the driver to surface pagination through the standard Laravel paginator contract. Verify how it reads the remote response's total, page, and per_page fields — many public APIs use different keys, and you may need to override the response-mapping method on the base class.
Auth and headers. Keep tokens in config/env, not in model classes. If the driver exposes a per-request header hook, use it in a base model shared by all your API models so rotation is one change.
Testing. Use Laravel's HTTP fake so tests never hit the network:
Http::fake([
'api.example.com/users/*' => Http::response(['id' => 1, 'name' => 'Ada'], 200),
]);
$user = ApiUser::find(1);
$this->assertSame('Ada', $user->name);
If the driver doesn't route through the Http facade, check for a swappable client — otherwise tests get brittle.
Abandoned and version-locked. Last release was 2020-09-23 and it targets Laravel 7. There is no evidence of Laravel 8+ / PHP 8 support. Before adopting, run it against your actual framework version in a scratch app; expect to either pin Laravel 7, fork the package, or vendor the base class into your project. For a 34-star, unmaintained package, vendoring is often the pragmatic choice.
HTTP N+1 is the main performance trap. foreach ($users as $user) { $user->profile; } can become one HTTP request per iteration. with('profile') only helps if the driver implements batched eager loading and the remote API supports filtering by many IDs at once. Check this explicitly — if it doesn't batch, disable lazy loading and load relations in explicit chunks.
Writes are not guaranteed. Do not assume save(), create(), update(), or delete() behave like Eloquent unless the README says so and the remote API supports them. There are no transactions, no rollback, no optimistic locking. Keep write paths in a real client/service class and use this package for reads.
404 vs null. Confirm whether a missing remote record throws ModelNotFoundException or returns null — Eloquent convention is the exception, but HTTP clients often return null. This difference silently breaks findOrFail-style code.
Casting and dates. Remote APIs frequently return strings for booleans, nested objects, or non-ISO dates. Declare $casts and $dates explicitly; don't rely on the driver to guess. Nested payloads need accessors that pull from the raw attribute array.
Timeouts, retries, rate limits. Default HTTP timeouts are often too long for request-path use and too short for slow batch jobs. Set them in config, add retry/backoff for 429/5xx, and respect Retry-After headers. If the remote API rate-limits, a cache is not optional.
Debugging. Log the outgoing URL, method, and status for every request during development — Laravel Telescope or Http::beforeSending/Http::after hooks give you this. Most "the model is broken" issues are actually a wrong endpoint path, a missing auth header, or an unexpected response envelope ({ "data": {...} } vs raw payload).
Extension points to inspect in src/:
data.where clauses — this is where unsupported operators will fail.Pin and isolate. If you adopt it, wrap all API models behind a shared abstract base class in your own code, so a future fork or replacement is a one-file change rather than a repo-wide refactor.
How can I help you explore Laravel packages today?