liberusoftware/module-manager-api
Laravel package providing a RESTful API for managing application modules. List, inspect, enable, and disable modules programmatically, with integration into the Liberu module manager for dynamic module administration.
This package is a presentation adapter, not the module manager itself. Expect it to contain HTTP routes, controllers, request validation, API resources and the bindings that translate API calls into the underlying module-manager domain services from the Liberu ecosystem. That framing matters: you will spend most of your time here exposing behaviour that already exists upstream, not implementing it.
Minimal setup:
composer require liberusoftware/module-manager-api
php artisan vendor:publish --provider="LiberuSoftware\ModuleManagerApi\ModuleManagerApiServiceProvider"
php artisan migrate # only if the core module manager ships migrations
php artisan route:list --path=api
Steps, in order:
src/Providers/*ServiceProvider.php tells you everything: route file location, config keys, middleware group, and whether routes are auto-loaded or gated behind a config flag.routes/. This is the real API contract. Map each route to a controller method before touching anything else.JsonResource. If they are not, note that as a red flag.php artisan tinker and resolve the module manager service/interface from the container. If it is unbound, you will get a container exception on the first request, not at boot.First use case: hitting GET /api/modules (or whatever the index route is named) to list installed modules, then GET /api/modules/{module} for a single module's metadata and status. From there, the enable/disable or install endpoints are the ones you will actually build UI against.
Because stars and dependents are effectively zero, treat documentation as non-existent. Your source of truth is routes/ + controller signatures + the composer require block, which tells you exactly which core package version this adapter was written against.
Thin adapter, thin controller. The correct pattern for this package is: validate in a FormRequest, hand a plain array or DTO to the domain service, return an ApiResource. Resist the urge to add logic here — anything you add in this layer becomes a merge conflict when the package bumps.
Interface binding / swap for tests. The adapter likely binds a domain port to a concrete implementation. In TestCase or a dedicated service provider, rebind that interface to a fake so your feature tests do not need real module state:
$this->app->bind(ModuleRegistry::class, fn () => new InMemoryModuleRegistry([...]));
That is far cheaper than seeding a real module directory per test.
Extend by composition, not by editing vendor. Add your own routes and controllers in routes/api.php of your app that call the same domain services, then reuse the package's JsonResource classes for consistent output. If you need extra fields, subclass the resource and override toArray():
class ModuleResource extends \LiberuSoftware\ModuleManagerApi\Resources\ModuleResource
{
public function toArray($request): array
{
return parent::toArray($request) + ['dependents' => $this->dependents()];
}
}
Version the surface early. If the package does not already prefix with /api/v1, add your own prefix in RouteServiceProvider or the config. Given the release cadence implied by the metadata, the JSON shape will move; a version prefix is the cheapest insurance you have.
Auth and authorization. These routes almost certainly ship unauthenticated. Wrap them in auth:sanctum (or your guard) via the package config or by re-registering the route group. Module enable/disable is a privileged operation — gate it with a policy or Gate::define('manage-modules') rather than relying on route middleware alone.
Idempotency for state-changing calls. Enable/disable/install should be safe to retry. If the package's endpoints are not idempotent, wrap them: dispatch a queued job with a deterministic key, or check current status before calling. This matters when a UI retries on timeout.
Tenant-scoped module availability. In a modular monolith like Liberu, module enablement is often per-tenant. Add middleware that resolves the tenant and passes it to the domain call, and key any cache on tenant ID:
Cache::remember("modules:{$tenant->id}", 300, fn () => $registry->allFor($tenant));
Contract tests over unit tests. Since you do not control the internals, test the HTTP shape: status codes, data structure, validation errors. Use assertJsonStructure rather than full assertExactJson so upstream additions do not break your suite.
Consuming from a frontend. Because the adapter emits resources, you can point an SPA or a Livewire component at it directly. Cache the module list client-side and invalidate on any 2xx from a mutating endpoint.
NOASSERTION. Composer will not stop you, but this is not a clear grant. Confirm with the maintainer before shipping in anything commercial; otherwise treat it as read-only reference code.composer.json ("liberusoftware/module-manager-api": "1.2.*" or a commit hash) and expect breaking changes on minor bumps.bootstrap/providers.php (Laravel 11+) or config/app.php, and check whether the package gates route registration behind a config flag that defaults to false./api/modules, route:list will show duplicates and the last-registered wins. Reorder providers or rename your prefix.php artisan config:cache after publishing, then php artisan optimize:clear whenever you change module state, otherwise the registry looks frozen.data (default JsonResource) or flattened. Frontends written against one shape silently break against the other.boot() (rather than lazily) will fail if the core package registers later. Defer everything.bootstrap/app.php (withExceptions) or the package's handler. Otherwise everything is a 500 and your API is unusable.Log::info(__METHOD__, func_get_args()) at the top of the controller method, hit the endpoint, and read storage/logs/laravel.log. With no docs, this is faster than reading source.Resource/Collection classes (usually swappable via config), route middleware arrays, and events dispatched around module state changes — listening to those is the cleanest way to hook in without touching vendor code.phpunit.xml via an env var, or rebind the registry fake as described above.GET /api/modules takes >50ms, add a cache layer in your wrapper, not in vendor, and invalidate it from a listener on the module-state-changed event.How can I help you explore Laravel packages today?