mahmoud-almalah/laravel-api-helpers
A lightweight Laravel package with practical helpers for building cleaner APIs. Reduce boilerplate, standardize common response and controller patterns, and keep your backend code consistent, readable, and easier to maintain across projects.
composer require mahmoud-almalah/laravel-api-helpers — the service provider is auto-discovered, so there is nothing to register in config/app.php or bootstrap/providers.php.^8.1 and Laravel 10, 11, or 12. Check composer.json constraints before adopting on a mixed-version monorepo.MahmoudAlmalah\LaravelApiHelpers\Responses:
SuccessResponse — single resource / simple payloadsErrorResponse — error envelopesCollectionResponse — collections, with or without paginationuse MahmoudAlmalah\LaravelApiHelpers\Responses\CollectionResponse;
use App\Http\Resources\UserResource;
return new CollectionResponse(
key: 'users',
collection: UserResource::collection(User::all()),
paginator: null,
message: 'Users fetched successfully'
);
UserController@index) and writing one feature test to pin down the exact JSON envelope (status, data vs key, meta, message). The v1.0.0 release notes describe the constructors but not the full response shape, so test the actual contract.Resource / ResourceCollection instances. Always pass UserResource::collection(...) or new UserResource($user) — not raw Eloquent results — so transformation logic stays in one place.CollectionResponse constructor mixes key, collection, paginator, and message; positional arguments are easy to get wrong and named args match the README exactly.$users = User::query()->paginate(25);
return new CollectionResponse(
key: 'users',
collection: UserResource::collection($users->getCollection()),
paginator: $users,
message: 'Users fetched successfully'
);
Confirm the exact paginator shape the package expects (a LengthAwarePaginator vs the original paginator) once in a test.
SuccessResponse; typically return new SuccessResponse(data: new UserResource($user), message: '...').ErrorResponse from bootstrap/app.php's withExceptions() closure to give API consumers the same envelope for validation, 404s, and auth failures:->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (Throwable $e, $request) {
if ($request->is('api/*')) {
return new ErrorResponse(message: $e->getMessage(), status: 500);
}
});
})
Verify the exact constructor signature before relying on status — the release notes don't list ErrorResponse parameters.
ApiController base or trait with helpers (respondSuccess(), respondError(), respondCollection()) that wrap these classes. This keeps controllers readable and isolates any future package changes to one file.ResponseInterface and service class. If your API needs a different envelope (e.g. always data + meta + links, or a custom trace_id), implement ResponseInterface in your own class rather than subclassing the shipped responses — safer for upgrades.response()->json(...) calls to the new classes behind those tests.withExceptions() last, since it affects every endpoint.vendor:publish will do nothing meaningful) and no per-project envelope configuration. If you need keys or envelopes other than what ships, you must extend via ResponseInterface.UserResource::collection(User::paginate()) and a paginator will likely duplicate the meta block or paginate twice. Fetch the builder's collection and paginator separately, and test the paginated case explicitly.return new ... from controllers, which implies the classes return JSON via toResponse() or Jsonable. Verify with $response->getStatusCode() and $response->getContent() in tests — especially the default status for SuccessResponse (likely 200) and ErrorResponse (needs to be explicit).ErrorResponse maps to HTTP codes. Be explicit about the status in the constructor if it is a parameter, and assert it in tests; otherwise every error may return 200.lang integration; if you need translated message fields, resolve them with __() before passing into the response./api/v2) or convert all endpoints in one PR.dump($response->json()) or dd($response->toResponse(request())->getContent()) will show the exact shape. If a test fails with a missing key, it is usually because the wrong response class was chosen (e.g. SuccessResponse where CollectionResponse was intended).composer.json ("mahmoud-almalah/laravel-api-helpers": "^1.0") and watch the changelog before upgrading in production.ResponseInterface and the underlying service class are the intended hooks. Prefer them over reflection or overriding vendor code when you need custom behavior.How can I help you explore Laravel packages today?