saritasa/dingo-api-custom
Saritasa's Dingo API Custom package extends Dingo API for Laravel projects with Saritasa-specific conventions, helping standardize API routing, responses, and integration points while reducing repetitive setup for Dingo-based services.
This package is a thin set of opinionated overrides for dingo/api — it does not add a new API layer, it rewires a handful of Dingo's container bindings so that responses, exceptions and transformers behave the way Saritasa's other packages and Laravel project templates expect. Adopt it only if you are already running Dingo/Api and want its defaults aligned, not as a reason to pull Dingo in.
What changed in 3.0.0: Laravel 11 support. That is the whole changelog, but it is a major bump, so treat it as a constraint break rather than a feature: the package now targets Laravel 11 (PHP 8.2+), and the 2.x line is the one that supports older Laravel majors. If you are on Laravel 8–10 you stay on 2.x; if you are upgrading a project to Laravel 11 you have to move to 3.x and re-verify everything below. There is still no signal that this package will track Laravel 12, so the "will I have to vendor a fork?" question is deferred, not answered.
Start with the constraint audit rather than the install:
composer show saritasa/dingo-api-custom --all # inspect requires/conflicts/replaces
What you are looking for in that output:
dingo/api version constraint 3.0.0 pins. Dingo itself has not moved quickly, so the Laravel 11 support here almost certainly comes with a specific dingo tag — confirm it is a real tagged release and not a dev branch.replace/conflicts against other Saritasa packages.illuminate/* bounds — "Laravel 11 support" usually means "Laravel 11 only", not "11 and below".Install it, then read the service provider before writing any code:
composer require saritasa/dingo-api-custom:^3.0
php artisan vendor:publish --provider="Saritasa\DingoApiCustom\Providers\DingoApiCustomServiceProvider"
(If the publish tag is not config, list tags with php artisan vendor:publish and pick the package's entry.) The provider's register()/boot() methods are the entire contract of the package — every behaviour it changes is visible there as an $this->app->extend(...) or $this->app->singleton(...) call. Note down which Dingo contracts it rebinds; those are the seams you will interact with later.
First use case: make a validation failure on an API route return a predictable JSON envelope instead of a redirect or an HTML error page. With Dingo plus these customizations the flow is:
// routes/api.php (Dingo router, not Laravel's)
$api->version('v1', function ($api) {
$api->post('users', 'App\Api\V1\Controllers\UserController@store');
});
// app/Api/V1/Requests/StoreUserRequest.php
class StoreUserRequest extends FormRequest
{
public function authorize(): bool { return true; }
public function rules(): array
{
return ['email' => 'required|email', 'name' => 'required|string'];
}
}
Then curl -X POST /api/users -d '{}' -H 'Accept: application/json' should yield a 422 with a machine-readable body rather than a stack trace. If you get HTML, the customization provider is not overriding Dingo's exception handler — check app()->getLoadedProviders() in tinker and provider order (see Gotchas).
Verify the Dingo stack itself is healthy first:
php artisan api:routes # Dingo's route list — must populate
php artisan api:cache # or explicitly confirm you are NOT using it
If api:routes is empty, the package is not the problem — your config/api.php and route registration are, and no amount of customization will fix that.
Override, don't fork. The package works by swapping Dingo's contract bindings. When you need project-specific behaviour beyond what it provides, extend the same seams rather than editing vendor code — your provider boots after the package provider (register it later in config/app.php or rely on discovery order) so your binding wins:
// app/Providers/ApiServiceProvider.php
public function register(): void
{
$this->app->extend(
\Dingo\Api\Contract\Debug\ExceptionHandler::class,
fn ($handler, $app) => new App\Api\ExceptionHandler($handler->getResponseFactory())
);
}
Response transformation layer. Dingo's Transformer\Factory plus binding is the normal path; keep the customization package's serializer defaults in mind so your transformer output matches what the package normalizes (e.g. whether a top-level data key exists):
$api->version('v1', function ($api) {
$api->get('users/{id}', [
'as' => 'api.users.show',
'uses' => 'App\Api\V1\Controllers\UserController@show',
]);
});
// controller
public function show(int $id)
{
return $this->response->item(User::findOrFail($id), new UserTransformer());
}
Mixing $this->response->item() (Dingo) with return new UserResource(...) (Laravel) in the same controller is the most common source of "the shape changed after I added one endpoint" bugs.
Errors as a first-class contract. The main day-to-day value of a customization package like this is a stable error payload. Centralise status mapping in one place rather than in each controller:
// App\Api\ExceptionHandler
protected function handleException(Exception $e): Response
{
return match (true) {
$e instanceof \Illuminate\Validation\ValidationException
=> $this->response->array(['message' => 'invalid', 'errors' => $e->errors()])->setStatusCode(422),
$e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException
=> $this->response->errorNotFound(),
default => parent::handleException($e),
};
}
Auth and middleware (Laravel 11 form). Dingo routes use Dingo's own middleware aliases (api.auth, api.throttle), which are registered separately from Laravel's auth. On Laravel 11 there is no app/Http/Kernel.php by default, so the aliases you used to put in Kernel::$routeMiddleware now go in bootstrap/app.php:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'api.auth' => \Dingo\Api\Http\Middleware\Auth::class,
'api.throttle' => \Dingo\Api\Http\Middleware\RateLimit::class,
]);
})
Make sure those aliases agree with Dingo's config/api.php middleware list — otherwise an endpoint looks protected but isn't.
Versioned routing is the natural home for package customizations. Put all version-scoped concerns (middleware, throttle, transformers) inside the $api->version() callback and keep cross-cutting ones in config/api.php. That way a v2 can be introduced without touching the override layer.
Testing. Use Dingo's dispatcher for internal API calls and plain $this->json() for HTTP-level tests:
$this->json('POST', '/api/users', [])->assertStatus(422)->assertJsonPath('message', 'invalid');
// internal call, bypasses HTTP layer
$response = app(\Dingo\Api\Dispatcher::class)->be($user)->post('users', $payload);
Test the error envelope explicitly — that is the part this package changes and the part least covered by Dingo's own suite.
illuminate/* and dingo/api bounds to have moved and older-Laravel support to be gone. Pin ^2.0 on Laravel 8–10 projects and ^3.0 on Laravel 11; do not let composer require silently rewrite your framework version.Illuminate\Foundation\Application::version() check or a removed kernel method was missed. Smoke-test the three responses (404/422/500) before you plan any feature work on top of it.api routes + FormRequest + JsonResource cover almost everything this package is smoothing over. Adopt only on inherited Dingo projects.DingoApiCustomServiceProvider, or your bindings get clobbered. Discovery order follows composer.json order — use explicit providers entries in bootstrap/providers.php (Laravel 11's replacement for the providers key in config/app.php) when order is load-bearing.php artisan config:cache after any config change. Dingo reads config/api.php heavily; a stale cached config is the #1 cause of "my customization stopped working in staging." Laravel 11's optimize command caches config and routes together — clear it, don't fight it.APP_DEBUG=true changes the error shape. Debug mode leaks traces through the handler you just customized. Assert on the production shape in tests (config(['app.debug' => false])).php artisan route:cache does not see Dingo routes; use php artisan api:cache if you need it, and remember it must be cleared on every deploy (api:cache → api:clear). Laravel 11's php artisan optimize will not do this for you.Class "Saritasa\DingoApiCustom\..." not found after upgrading to 3.x, the provider/directory layout may have changed — confirm against the provider FQCN in the package's composer.json extra.laravel.providers before touching your own config.composer why saritasa/dingo-api-custom → confirm Laravel ↔ package major line matches → api:routes → hit a 404, a 422 and a 500 → confirm all three use the same envelope → only then start feature work.How can I help you explore Laravel packages today?