Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Api Response Laravel Package

dragon-code/api-response

Build consistent Laravel API responses with a fluent, macro-friendly builder. Dragon Code's api-response helps you return success and error payloads, HTTP status codes, and resource collections in a uniform JSON shape, cutting boilerplate and keeping endpoints predictable.

View on GitHub
Deep Wiki
Context7

Getting Started

Correction to the previous assessment: this package is not archived. v10.0.0 follows v9.1.0, the maintainer (andrey-helldar) is actively shipping, and the docs were refreshed in the same release. The earlier note that the current line was 1.x was also wrong — if anything in your ADR or notes pins dragon-code/api-response:1.x, that resolves to something years stale. The current line is 10.x.

So treat v10 as a routine major of a live package rather than a corpse. The one thing to be deliberate about: the only functional change listed is the bump of dragon-code/support to 6.0. That is a transitive major, so the real breaking surface is whatever support 6.0 changed, not this package's own API.

composer require dragon-code/api-response:^10.0
composer why dragon-code/support

Then read vendor/dragon-code/support/CHANGELOG.md for 5.x → 6.0. Helper signature changes and a raised PHP floor are the usual candidates — a support-library major is exactly where a minimum PHP version gets bumped, so check composer.json constraints before you run the update on an older runtime.

Where to look first: src/ for the class that actually composes the payload, plus the config file if one ships (config/api-response.php). Confirm the registration surface first — whether it's auto-discovered by Laravel package discovery or needs an explicit provider (or a bundle, if you're on the Symfony side of this codebase). The docs updated in this release (PR #76) are now the fastest source of truth and will be more current than any blog post written against an earlier major.

First use case: convert exactly one controller.

public function show(int $id): JsonResponse
{
    return ApiResponse::success(UserResource::make($this->users->findOrFail($id)));
}

Before you convert, write down the envelope you're committing to (data / errors / message / code) and get sign-off. That shape becomes a public API contract the moment your first mobile client ships, and it is far more expensive to change later than the library behind it.

Implementation Patterns

Wrap it. Always. The wrapper service is what makes a major bump like v9 → v10 a non-event: one file changes, fifty controllers don't.

final class ApiResponder
{
    public function success(mixed $data = null, int $status = 200): JsonResponse
    {
        return ApiResponse::success($data, $status);
    }

    public function error(string $message, int $status = 400, array $errors = []): JsonResponse
    {
        return ApiResponse::error($message, $errors, $status);
    }
}

Convert exceptions in one place. In Laravel 11+ that's bootstrap/app.php → withExceptions(); on 10 and below, app/Exceptions/Handler.php. Map HttpExceptionInterface to its status code, everything else to 500, and let the package format the body so no uncaught throwable ever returns an HTML error page to an API client:

$status = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500;

$message = $status >= 500 && !config('app.debug')
    ? 'Internal server error'
    : $e->getMessage();

return $this->responder->error($message, $status);

The debug branch is not optional — do not let the package echo $e->getMessage() on a 500 in production.

Map validation failures explicitly, and note that Laravel does half the work for you. ValidationException::errors() already returns a field-keyed array<string, string[]>, which is usually exactly the shape you want:

if ($e instanceof ValidationException) {
    return $this->responder->error('The given data was invalid.', 422, $e->errors());
}

Wire this in the same exception handler as above so a manual Validator::make() failure and a FormRequest failure render identically.

Let your resources serialize; let the package envelope. Run models through JsonResource / ResourceCollection (or symfony/serializer if you're on the Symfony side) and hand the package the normalized array or resource. This keeps group exposure, circular-reference handling, and date formatting in the framework, where it's documented and maintained — and it avoids an archived-era surprise where an entity with lazy proxies attached gets json_encode'd raw.

Set status codes explicitly. If success() defaults to 200 regardless of verb, POST endpoints return 200 where clients expect 201. Pass the status every time rather than relying on a default you haven't verified in this major.

Assert the envelope in functional tests. One base test case with an assertApiEnvelope() helper checking top-level keys and Content-Type: application/json catches the day someone returns a bare response()->json() and quietly breaks the contract.

Gotchas and Tips

v10's breaking change is transitive, so your test suite is the real changelog. Run the full suite after composer update --with-dependencies dragon-code/api-response, and skim the dragon-code/support diff. If something breaks, composer why-not dragon-code/support 6.0 will tell you what else is holding the old major back — that's usually the actual blocker, not this package.

Double-wrapping is the most common failure, and in Laravel it's usually JsonResource. JsonResource already wraps its payload in data, so passing one into a package that also wraps produces {"data":{"data":{...}}}. Either unwrap with ->resolve() or configure the resource's $wrap. Also check for a custom kernel.response / middleware that rewrites bodies — only one thing should own the response body.

[] vs {}. An empty data array serializes as [], not {}. Strict clients (TypeScript with data: object) will choke. Decide once whether empty collections become [] or null, and enforce it in your wrapper rather than at fifty call sites.

204 No Content must not go through the responder. A DELETE returning 204 with a JSON body is invalid. Short-circuit those before the package gets involved.

Verify the config keys, don't copy them. Publish the config (php artisan vendor:publish — check the tag) and compare against the current docs. Examples in the wild are now several majors behind and will happily hand you keys that silently do nothing.

Extension points, in order of preference:

  1. Your own wrapper service — safest, survives everything, including the next major.
  2. Container binding / decoration ($this->app->extend(...), or decorates: + #[AsDecorator] on the Symfony side) to override one formatting method without forking.
  3. Fork and use a VCS repository in composer.json ("repositories": [{"type": "vcs", ...}]) — now a last resort rather than the default, since upstream is alive.
  4. Alias swap — rebind the package's service id to your own class. Blunt but effective; verify the id exists in the container first.

Debugging checklist when a response looks wrong: dump the envelope from your wrapper (not from inside the package), confirm exception-handler ordering, and set APP_DEBUG=0 locally before claiming prod error messages are safe — the debug branch is invisible in dev.

Revisit the build-vs-buy call, but the answer has shifted. The previous assessment argued the package's entire job is composing an array into JsonResponse, so ~60 lines of your own code would do. That's still true, and still worth weighing if you're greenfield. But "unmaintained, archived, no Symfony 6/7 path" was the strongest argument against it, and that argument is now gone: v10 is current, docs are current, and the upgrade treadmill is being walked by someone else. If you already have an integration, keep the wrapper and stay; if you're starting fresh, either choice is defensible now — the wrapper is what keeps it that way.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource