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

Laravel Responder Laravel Package

flugger/laravel-responder

Laravel Responder builds clean, consistent API responses with Fractal transformers, automatic exception handling, and flexible serialization. Turn Eloquent models into well-structured JSON with minimal effort and full control.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require flugger/laravel-responder
php artisan vendor:publish --provider="Flugg\Responder\ResponderServiceProvider"

On Laravel 5.5+ the service provider and Responder facade are auto-discovered. On older versions add Flugg\Responder\ResponderServiceProvider to config/app.php and alias 'Responder' => Flugg\Responder\Facades\Responder::class. Publishing gives you config/responder.php and the error/message language files.

Where to look first

  1. config/responder.php — serializer choice, pagination behaviour, error-code → status-code map, and query-parameter handling. Almost every "why does my JSON look like this?" question is answered here.
  2. vendor/flugg/responder/src/Transformers/Transformer.php — the base class your transformers extend; note item() / collection() helpers and the include hooks.
  3. vendor/flugg/responder/src/Http/Responses/ — SuccessResponseBuilder and ErrorResponseBuilder, which are macroable and Responsable.

First use case (10 minutes)

Write one transformer, return it from one controller:

namespace App\Transformers;

use App\Models\User;
use Flugg\Responder\Transformers\Transformer;

class UserTransformer extends Transformer
{
    public function transform(User $user): array
    {
        return [
            'id'    => (int) $user->id,
            'name'  => $user->name,
            'email' => $user->email,
        ];
    }
}
use App\Models\User;
use App\Transformers\UserTransformer;

public function show(User $user)
{
    return responder()->success($user, UserTransformer::class);
}

Hit the route and you get {"status":"success","data":{...}} — a consistent envelope with zero hand-rolled response()->json() calls. From there, the second thing to try is responder()->success(User::paginate(), UserTransformer::class) and watch the pagination block appear automatically.

Implementation Patterns

Prefer the resolver helper over the facade in controllers. Inject Flugg\Responder\Responder via the constructor or use the responder() helper. Both give you the same builder and keep controllers testable. Returning the builder directly works because it implements Responsable.

Be explicit about shape when it is ambiguous. success() guesses: paginators get paginated, collections get wrapped as a list, models as a single item. Guessing gets it wrong for ->first() on a hasMany, for a one-element array, or for a CursorPaginator. Use the dedicated methods:

responder()->item($user, UserTransformer::class);
responder()->collection($users, UserTransformer::class);
responder()->paginator(User::paginate(25), UserTransformer::class);

Drive relationships through include, never by dumping them in transform(). This is the pattern that pays for the package:

class UserTransformer extends Transformer
{
    protected $availableIncludes = ['posts', 'company'];

    public function includePosts(User $user)
    {
        return $this->collection($user->posts, PostTransformer::class);
    }

    public function includeCompany(User $user)
    {
        return $this->item($user->company, CompanyTransformer::class);
    }
}

Clients then control payload size with ?include=posts,company. Nested includes (?include=posts.comments) fall out automatically as long as PostTransformer declares them. Keep $defaultIncludes nearly empty — anything there is loaded on every request whether the caller wants it or not.

Eager-load in the query, transform in the transformer. Responder/Fractal has no idea what your includes will be until after the query ran, so lazy-loading in includePosts is the classic N+1 trigger. Load the union of what's plausibly requested on the controller side (->with(['posts', 'company'])), or gate expensive includes behind authorization.

Drop the transformer argument via the factory. Binding in a service provider boot() is what makes Responder feel like Laravel rather than Fractal:

use Flugg\Responder\Transformers\TransformerFactory;

public function boot(TransformerFactory $factory): void
{
    $factory->bind(User::class, UserTransformer::class);
}

Then responder()->success($user) resolves UserTransformer by model class. Bindings are keyed by class name, so remember that a subclass of User will not match a User::class binding unless you register it separately.

Standardise success payloads with meta and status codes. ->meta(['total' => $count]) for side-band data, ->statusCode(201) for creates. If your API always includes a "message", wrap it once as a macro on the success builder in a service provider rather than repeating it in every controller.

Errors: throw, don't return. Register Responder's exception conversion (its handler hooks convert validation, auth, and 404 exceptions into the error envelope) and then just use Laravel's normal flow — abort(404), $this->authorize(...), FormRequest validation. For domain errors use Responder's error codes:

return responder()->error('payment_declined')->data(['reason' => $reason]);

Each code maps to an HTTP status (and message translation) in config/responder.php. Treat that map as the public contract: add codes deliberately, document them in your API docs, and never renumber one a client already handles.

Compose transformers, don't nest arrays. Inside a transformer, $this->item() and $this->collection() produce proper Fractal resources, so PostTransformer output inside UserTransformer stays consistent with the standalone PostTransformer output. Copying toArray() logic into a nested array is how APIs drift.

Test at two levels. Unit-test transformers directly ((new UserTransformer)->transform($user)) — fast, no HTTP. Integration-test the envelope with assertJsonPath('status', 'success') and assertJsonStructure(['status', 'data' => [['id', 'name']]]). When changing serializers, expect every integration assertion to need a rewrite.

Gotchas and Tips

The serializer decision is a one-way door. The default Fractal ArraySerializer gives data/meta envelopes; switching to JsonApiSerializer reshapes everything into JSON:API type/id/attributes/relationships, and your transformers will need $resourceKey. Decide before you have consumers; changing later is a breaking API version, not a config tweak.

Clients must send Accept: application/json. Without it, Laravel renders validation and auth failures as HTML redirects/errors instead of going through the JSON exception conversion — and you'll get "the API randomly returns HTML" bugs that are actually client-side.

Unknown error codes fail loudly. responder()->error('typo_code') won't quietly 500-with-a-nice-message; it throws because the code isn't in the map. Keep error codes in a constants class or enum and reference them from both the config and your controllers.

success([]) and success(new Collection()) are different animals. An empty Eloquent collection is still a collection (list data: []); an empty plain array may not be detected the way you expect. Use collection() when you mean "list, possibly empty".

Include strings are user input. They are validated against $availableIncludes, but the query cost is not. A client requesting ?include=posts.comments.author.company on a poorly eager-loaded endpoint is a slow-query DoS. Cap include depth or restrict available includes per endpoint.

meta() merges into the envelope, so key collisions silently win. Don't put data or status in meta; you'll shadow the envelope keys and spend an afternoon on it.

Pagination keys depend on the paginator type. LengthAwarePaginator gets the full meta.pagination block; SimplePaginator, CursorPaginator, and hand-built arrays either get partial data or fall through to plain-collection handling. If you use cursor pagination, verify the output shape explicitly rather than assuming.

Debugging the envelope is easier below the facade. dd(responder()->success($user, UserTransformer::class)->toResponse(request())) shows the final JSON; app('flugg.responder.fractal') (the Fractal manager) lets you inspect serializer and include parsing when an include silently disappears. Also check that the transformer you passed is actually the one being resolved — an unbound or mistyped class name falls back to the factory and can yield surprising output.

Response builders are macroable — use that instead of a base controller. A ResponseFactory-style service or a SuccessResponseBuilder::macro('withTrace', ...) keeps cross-cutting envelope concerns in one place. Subclassing controllers to share response helpers tends to metastasise.

Maintenance risk is real. 888 stars but effectively zero downstream dependents and low opportunity score: this is a small, single-maintainer wrapper around league/fractal. Pin the version in composer.json, keep your usage concentrated in a handful of controllers/transformers, and be prepared to fork or inline the ~few hundred lines if Fractal/PHP compatibility breaks. Avoid spreading responder() calls into 100 files — that's what makes the fork expensive.

Don't mix paradigms. If half the API returns response()->json() and half returns the Responder envelope, clients need two parsers and your OpenAPI schema becomes a lie. If you adopt Responder, adopt it for every endpoint, including health checks and errors.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager