pdfloresjdav/api-bundle
A Laravel/PHP bundle that streamlines building REST APIs, with organized routing, controllers, request validation, and consistent JSON responses out of the box. A practical starting point for teams wanting a structured, reusable API layer without boilerplate.
Step 0 — verify it actually boots in Laravel before writing any code against it. The description claims a Laravel-native service provider, but the DYB naming and *Bundle suffix are Symfony conventions. One minute of verification saves a day of pain:
composer require pdfloresjdav/api-bundle
php artisan package:discover # does a service provider register?
cat vendor/pdfloresjdav/api-bundle/composer.json | grep -E '"type"|symfony|illuminate'
If type is symfony-bundle or the requires are symfony/*, stop and treat the source as reference code — port the DTOs and response logic into your own app/Support/Api/ namespace instead of depending on it.
Where to look first (in this order):
composer.json — PHP/Laravel version constraints; this is the first thing that breaks on current majors.src/*ServiceProvider.php — what gets registered globally (routes, middleware, exception handlers).config/*.php (or the publish tag) — the real configuration surface; this is what you will live in.First use case — scaffold one versioned read endpoint and one write endpoint against a throwaway model. That exercises routing, serialization, success envelope, validation error envelope, and exception handling in a single afternoon. If all five work, you have a bounded baseline and can decide. If any of them require reading internals to understand, that is your signal to fork rather than depend.
php artisan vendor:publish --provider="Pdfloresjdav\ApiBundle\...ServiceProvider"
php artisan route:list --path=api
curl -i http://localhost/api/v1/ping
The package's value is standardized response shape and transformers. Lean into it: keep controllers thin, put serialization in the package's transformer layer, and let the envelope be the contract your frontend codes against.
// routes/api.php — rely on the package's versioned group if it registers one
Route::prefix('v1')->middleware('api')->group(function () {
Route::apiResource('orders', OrderController::class);
});
// app/Http/Controllers/Api/OrderController.php
public function show(Order $order)
{
return $this->respond(OrderTransformer::make($order)); // package envelope
}
The win is consistency across endpoints. The trap is re-implementing Laravel's JsonResource alongside it — pick one serialization path and stick to it.
Do not let package response helpers leak into domain code. Introduce a thin base controller (or trait) that delegates:
abstract class ApiController
{
protected function ok($data, int $status = 200) { /* delegates to package */ }
protected function fail(string $code, string $message, int $status) { /* delegates */ }
}
When the package is abandoned — and with 0 stars/0 dependents that is the default expectation — you swap one file, not every controller.
The package's error envelope should consume Laravel's validation exception, not replace it. Keep standard FormRequest validation and confirm the package's exception handler formats the 422 payload into its envelope. If it registers a global exception handler via the service provider, verify it does not clobber your own App\Exceptions\Handler customizations — check bootstrap/app.php / Handler::render after install.
Transformer classes are usually just PHP that maps an Eloquent model to an array. Even in "adopt" mode, prefer composing them with ->load() and ->loadCount() eager loads yourself so you control N+1:
$order = Order::with('customer', 'items.product')->withCount('items')->findOrFail($id);
return $this->ok(OrderTransformer::make($order));
Treat the package transformer as a formatting function, not an ORM abstraction. That keeps it portable if you later fork.
If the package provides a versioned prefix, use it as the actual extension point: new versions get new transformer classes and new route files, but the same base ApiController. Do not branch inside transformers on version — it compounds quickly.
Write one feature test per endpoint that asserts the JSON envelope shape (data, meta, errors, status codes). Because the envelope is supplied by an unmaintained package, that test is your upgrade tripwire when you eventually swap it out.
*Bundle naming and DYB description are red flags; verify the service provider boots before architecting around it.laravel/framework in composer.json and pin the package to an exact version ("1.2.3", not "^1.2").api.php, App\Exceptions\Handler, or bootstrap/app.php. Run php artisan route:list and diff it before and after install.config/api.php is yours; upstream config changes will not propagate. That is fine, but note it — you are now maintaining it.php artisan config:clear (dev) and php artisan config:cache (prod) — a stale cache is the most common "why is my prefix still v0" bug.JsonResource or ValidationException defaults. Decide early whether the package's envelope is authoritative; if it is, disable or adapt App\Exceptions\Handler custom formatting to avoid double-wrapping errors.JsonResource on the same model. Mixed serialization is the fastest way to inconsistent payloads.dd($transformer->toArray($model)) is more reliable than reading the source; if the output is surprising, check whether $hidden, $visible, $appends, and $with on the model are being respected or bypassed.DB::listen in a local run and assert query counts in tests./api/v1 group, do not also define Route::prefix('v1') in api.php — you will double-prefix (/api/v1/v1/...) or shadow routes. Pick one owner.DependencyInjection/ and Symfony wiring if present, add mergeConfigFrom + publishes, re-namespace to App\Support\Api\, and require via a vcs repository entry. Budget a day; ship a ApiEnvelope contract test in the same PR.How can I help you explore Laravel packages today?