ellipsesynergie/api-response
A Laravel package for building consistent JSON API responses. It offers success and error helpers, HTTP status and custom error-code handling, Fractal transformer integration, and pagination support, so you can standardize API endpoints with less boilerplate.
Install the package and let Laravel discover the service provider on versions that support package discovery:
composer require ellipsesynergie/api-response
On older Laravel versions, register the provider and optional facade manually:
// config/app.php
'providers' => [
EllipseSynergie\ApiResponse\LaravelServiceProvider::class,
],
'aliases' => [
'ApiResponse' => EllipseSynergie\ApiResponse\Facades\Laravel::class,
],
Publish the config if you need to change the error envelope:
php artisan vendor:publish --provider="EllipseSynergie\ApiResponse\LaravelServiceProvider"
Look first at:
config/api-response.php — response/error formatting.EllipseSynergie\ApiResponse\Contracts\Response — the contract you should inject.EllipseSynergie\ApiResponse\Laravel\Response — the default Laravel implementation.The first useful endpoint is a single-resource response. Create a Fractal transformer:
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user): array
{
return [
'id' => (int) $user->id,
'name' => $user->name,
'email' => $user->email,
];
}
}
Then inject the response contract and return an item:
use EllipseSynergie\ApiResponse\Contracts\Response;
class UserController extends Controller
{
public function __construct(private Response $response)
{
}
public function show(User $user)
{
return $this->response->withItem($user, new UserTransformer);
}
}
This gives you a consistent data envelope and lets Fractal handle serialization.
Injection keeps controllers testable and makes it easy to swap in a custom response implementation:
abstract class ApiController extends Controller
{
public function __construct(protected Response $response)
{
}
}
Then extend it for API controllers.
public function index()
{
return $this->response->withPaginator(
User::paginate(25),
new UserTransformer
);
}
public function store(StoreUserRequest $request)
{
$user = User::create($request->validated());
return $this->response->withItem($user, new UserTransformer, 201);
}
public function show(User $user)
{
return $this->response->withItem($user, new UserTransformer);
}
public function destroy(User $user)
{
$user->delete();
return $this->response->withSuccess('User deleted');
}
Use withPaginator() rather than withCollection() for paginated results so pagination metadata is included.
return $this->response->withArray([
'user' => (new UserTransformer)->transform($user),
'token' => $token,
]);
Use this for compound payloads such as login responses, health checks, or small configuration endpoints.
return $this->response->withError('User not found', 404);
For exceptions, map them in App\Exceptions\Handler::render() so API clients always receive the same envelope:
public function render($request, Throwable $e)
{
if ($request->expectsJson() && $e instanceof ModelNotFoundException) {
return app(\EllipseSynergie\ApiResponse\Contracts\Response::class)
->withError('Resource not found', 404);
}
return parent::render($request, $e);
}
Also handle validation exceptions there if you want validation errors to use the same structure as other API errors.
Create a subclass of the package response if you need a different envelope, extra metadata, or custom error handling:
namespace App\Support;
class ApiResponse extends \EllipseSynergie\ApiResponse\Laravel\Response
{
// Override response-building methods as needed.
}
Rebind the contract in a service provider:
$this->app->bind(
\EllipseSynergie\ApiResponse\Contracts\Response::class,
\App\Support\ApiResponse::class
);
This is the main extension point. Prefer it over editing vendor files.
Put these endpoints behind your API route group and ensure clients send Accept: application/json. For browser-facing or mixed routes, add middleware that forces JSON when the request is under your API prefix:
if ($request->is('api/*')) {
$request->headers->set('Accept', 'application/json');
}
This keeps Laravel validation and exception handling in JSON mode.
$response->assertStatus(200)
->assertJsonStructure([
'data' => ['id', 'name', 'email'],
]);
For errors, assert the configured error keys as well as the HTTP status.
withCollection() and withPaginator().TransformerAbstract instance, e.g. new UserTransformer, not UserTransformer::class.withCollection() returns a Fractal collection; withPaginator() adds pagination metadata. Passing a paginator to withCollection() loses that metadata.200 for created or deleted resources. Pass a status code where the method supports it, or set it before building the response.config/api-response.php. Customizing keys can break existing clients, so version your API or keep the default envelope stable.php artisan config:cache, edits to config/api-response.php will not take effect until you run php artisan config:clear and re-cache.composer.json. Do not assume the latest release date means it is actively maintained.Response contract.response()->json() from the same endpoint family. Pick one convention per API surface.getContent() or getData(true) on the returned response to inspect the final envelope, including Fractal’s data wrapper.How can I help you explore Laravel packages today?