liberusoftware/developer-experience-api
A Laravel package that exposes an API for Liberu developer experience tooling, with endpoints and helpers intended to streamline local development workflows. Early-stage project with minimal adoption and limited documentation, so review the source before relying on it in production.
1. Confirm what you're actually installing.
At 0 stars and 0 dependents, this package has no downstream validation. Before composer require, open the repo and read these three files in order:
composer.json — check the require block for the Laravel version constraint (illuminate/*) and, critically, for sibling liberusoftware/* packages. Presentation adapters in this family almost always depend on a shared contracts/support package, which drags in a transitive graph you'll need to audit.module.json (or the extra.laravel block) — tells you whether this is a module (registered by a module loader such as nwidart/laravel-modules or an in-house equivalent) or a plain service-provider package. This changes your registration step entirely.src/Providers/ — find the main provider and read register()/boot() before you run anything. What it publishes, what it binds, and what routes it loads are all decided there.2. Install and register.
composer require liberusoftware/developer-experience-api
If the package ships auto-discovery, the provider registers itself. If it's a module in the Liberu sense, you'll need the host app's module loader enabled and the module placed/registered in the modules path. Check with:
php artisan about
php artisan route:list --path=api
php artisan vendor:publish --provider="Liberu\DeveloperExperienceApi\...ServiceProvider"
3. Look at these locations first, in this order.
| Path | Why |
|---|---|
routes/api.php (or routes/*.php) |
Reveals the actual URI surface, middleware stack, and versioning scheme — the single most informative file. |
src/Http/Controllers |
Shows whether it's a thin controller over a service, or a resource controller over a repository. |
src/Http/Resources / src/Presenters / src/Adapters |
This is the "presentation adapter" core — the mapping from domain object → API payload. |
src/Contracts |
The extension points you're allowed to implement. Anything not behind a contract is internal. |
config/*.php |
Feature flags, route prefixes, adapter bindings. |
tests/ |
If present, the tests are the de facto documentation — read them before the README. |
4. First use case: put a JSON façade over an existing module.
The intended day-to-day win of a presentation adapter is that you expose data from a domain module over HTTP without editing that module. Concretely:
Do this on one trivial resource first (a show/index endpoint with no auth) so you learn the shape of the contract before wiring authentication, pagination, and filtering.
Treat every adapter as a pure, side-effect-free function DomainObject → array. Keep it in one class, keep it dependency-light.
final class ProjectAdapter implements PresentsResource
{
public function present(object $model): array
{
return [
'id' => $model->uuid,
'name' => $model->name,
'status' => $model->status->value,
'owner' => $model->owner?->only(['id', 'name']),
'created_at' => $model->created_at?->toIso8601String(),
];
}
}
The value of this indirection is that when the API shape and the domain shape diverge (they always do), only the adapter changes.
If the package exposes an adapter registry, use it. It gives you per-environment overrides and makes tests trivial.
// config/developer-experience-api.php
'adapters' => [
\App\Models\Project::class => \App\Api\Adapters\ProjectAdapter::class,
],
Then in tests you can swap in a fixture adapter without touching the controller.
If the package supports route groups, mount v1 and v2 as separate route files each pointing at separate adapter sets. Resist if ($version === 2) inside an adapter — it collapses the abstraction you installed the package for.
Route::prefix('v1')->group(base_path('routes/api/v1.php'));
Route::prefix('v2')->group(base_path('routes/api/v2.php'));
The adapter should never decide who can see what. Wire auth:sanctum (or the host app's guard) at the route group, then call $this->authorize() in the controller against an existing policy for the underlying model.
Feature tests against the route are the highest-value tests here, because the adapter's only job is the response shape.
it('presents a project', function () {
$project = Project::factory()->create();
$this->actingAs(User::factory()->create())
->getJson("/api/v1/projects/{$project->id}")
->assertOk()
->assertExactJson([
'data' => [
'id' => $project->uuid,
'name' => $project->name,
'status' => $project->status->value,
'owner' => null,
'created_at' => $project->created_at->toIso8601String(),
],
]);
});
assertExactJson (not assertJson) is deliberate — it catches accidental field leakage, which is the most common bug in presentation layers.
With no community adoption, the honest highest-value use is as a pattern reference: read its adapter/contract pair, then reimplement the two or three classes you actually need in your own app/Http/Resources. That gives you Laravel's first-party JsonResource support (which the package may or may not use) and removes a dependency you cannot support.
->paginate() so meta/links come from Laravel rather than hand-rolled arrays.with()) in the controller or a repository.Licence is NOASSERTION. This is the single biggest blocker for production use. It means GitHub could not match a recognised licence file — the code may be proprietary, unlicensed, or have a malformed licence header. Run it past whoever owns legal at your org before it ships anywhere that matters. Absent an explicit licence, default copyright applies and you have no granted rights to redistribute.
Zero stars, zero dependents, no issue history. There is no one to ask. Treat every behaviour as undocumented and verify it yourself with a throwaway route. Budget for the possibility that you are the maintainer now.
The Last release: 2026-10-26 date is anomalous. Either the tag was created with a future-dated system clock or the metadata is unreliable. Pin to an explicit commit or tag in composer.json rather than ^x.y — you cannot trust the version graph.
Module loader vs. service provider confusion. This family of packages is inconsistent: some are nwidart/laravel-modules modules, some are plain providers. If auto-discovery "works" but routes 404, the module loader is probably required. Check bootstrap/cache/packages.php after install — if the package isn't listed there, discovery failed silently.
Provider boot order. If the package binds an adapter registry in register() and reads config in boot(), and your app's config is cached, you'll get stale bindings. php artisan config:clear after every config edit; never debug this with cache enabled.
Route collisions. An api prefix plus a host app that also owns /api is a classic 404/405 source. Run php artisan route:list --path=api -v and look for duplicate URIs and overlapping middleware. Prefer configuring a distinct prefix (api/dx) if the package allows it.
Middleware stack is inherited, not owned. The package's routes get whatever Route::middleware('api') resolves to in the host app. If auth "just works" in one project and 401s in another, this is why — check app/Http/Kernel.php / bootstrap/app.php.
Exception rendering is silent. Presentation adapters typically don't define their own exception handler, so a ModelNotFoundException may return an HTML error page inside a JSON API. Verify the 404/422 shapes explicitly and add a renderable handler if the package doesn't.
N+1 in adapters is invisible until production. An adapter that touches $model->owner->team per row will do 2 queries per item. DB::listen() or Laravel Debugbar on the index endpoint will show it immediately; the endpoint will look fine with 5 seeded rows.
Don't let the adapter become the domain. The moment an adapter contains a ->where() or a ->save(), you've recreated the coupling the package was supposed to remove. If you find yourself needing queries, the layering is wrong — push them into the controller or a repository.
Extension points are the contracts directory only. Anything else (src/Support, src/Concerns) is internal and may change in any commit, given there's no release discipline to speak of. If you must reach into internals, wrap the call in your own class so a future update touches one file.
Debugging checklist, in order: route:list → vendor:publish and read the config → dump the adapter output → config:clear → check packages.php. Most failures are step 2 or step 4.
How can I help you explore Laravel packages today?