andrey-helldar/api-response
Laravel package for building consistent JSON API responses. It offers convenient helpers for success and error replies, standardizes response structure, status codes, and messages, and helps reduce repetitive boilerplate in controllers and resources.
composer require dragon-code/api-response
Laravel auto-discovers DragonCode\ApiResponse\ServiceProvider, so there is nothing to register. Publish the config before you write your first endpoint:
php artisan vendor:publish --tag=api-response # check `php artisan vendor:publish` list if the tag differs in your version
Look at config/api-response.php first — it holds the envelope settings (default format, content negotiation toggle, message/localization options) and is the only file you will normally need to touch. Second stop: vendor/dragon-code/api-response/src/Facades/ApiResponse.php — the docblock lists every available helper, which is faster than the README for discovering the full method surface.
First use case — make one controller return the standard envelope:
use DragonCode\ApiResponse\Facades\ApiResponse;
public function show(int $id)
{
return ApiResponse::success(UserResource::make(User::findOrFail($id)));
}
public function store(StoreUserRequest $request)
{
return ApiResponse::created(UserResource::make(User::create($request->validated())));
}
v10 note: the release itself is only a dependency bump (
dragon-code/support5.x → 6.x) plus docs. That transitive major drives the requirements: expect PHP 8.2+ and a modern Laravel line. Runcomposer why-not dragon-code/support 6.0if resolution fails — the answer is almost always your PHP version or an older siblingdragon-code/*package.
Wrap at the controller, not the model. The package formats the envelope; it does not transform payloads. Keep JsonResource / ResourceCollection doing the data shaping and let ApiResponse do the envelope. Nested wrapping is the most common smell in codebases using this package.
Centralise error rendering in the exception handler. The highest-value integration is bootstrap/app.php (->withExceptions(...)) or app/Exceptions/Handler.php::render():
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->is('api/*') && ! $e instanceof ValidationException) {
return ApiResponse::error($e->getMessage())->setStatusCode(500);
}
});
})
This guarantees that 404s, 500s and auth failures from middleware share the same shape as your hand-written endpoints — otherwise your frontend has to branch on two response formats.
Build a thin base controller or trait if you have many endpoints, so status/message conventions (e.g. always localised messages via __()) live in one place rather than being repeated per action.
Content negotiation is opt-in. With it enabled, the same route can return JSON, XML or YAML based on the Accept header. That is genuinely useful for legacy/enterprise consumers, but treat it as a deliberate feature: gate it behind config, and if you enable it, add a feature test per format because it silently changes the response body for any client that sends an unusual Accept.
Testing. Assert the envelope, not the whole body:
$response->assertOk()
->assertJsonPath('status', 200)
->assertJsonStructure(['type', 'status', 'message', 'data' => ['id', 'name']]);
Write one of these per format if negotiation is on, and one for the exception handler path.
Versioning / OpenAPI. Because the envelope is uniform, you can describe it once as a schema component in your OpenAPI spec and $ref it everywhere. Do that early — it is the cheapest payoff of adopting the package.
dragon-code/support 6.0 can raise the floor on PHP/Laravel. Upgrade all dragon-code/* packages in one commit; mixed 5.x/6.x support constraints will deadlock Composer.ApiResponse::success() defaults to 200; use created() / noContent() / the explicit status argument rather than ->setStatusCode() sprinkled around, or your status field and the real HTTP code will drift apart (very confusing for clients that read only one of them).Accept: */* and text/html. Browsers and some HTTP clients send these by default. If negotiation is on, verify what a browser hitting your API gets — you do not want an XML body reaching a JS fetch that assumed JSON.response()->json() and part returns ApiResponse, document the split or migrate fully. Half-adopted envelopes are worse than none.ApiResponse::success(['x' => 1])->getContent() in php artisan tinker shows the exact body and is faster than curling a route. If a response looks unstyled, check whether an exception was thrown before your ApiResponse call and got rendered by the default handler instead.ResponseMacroable-style macro for missing verbs. Prefer (a) and (c); (b) means you own upgrades.lang/*/api-response.php (or your own keys) is where wording lives — do not hardcode English messages in controllers if you ship more than one locale.How can I help you explore Laravel packages today?