haseebmirza / laravel-api-response
A simple and consistent API response helper for Laravel applications.
Package info
github.com/haseebmirza/laravel-api-response
pkg:composer/haseebmirza/laravel-api-response
Requires
- php: ^8.1
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.0|^11.0
README
A lightweight, zero-config package that gives your Laravel API a consistent JSON response format — so every endpoint speaks the same language.
Why?
Without a standard response helper, API responses across your app end up looking different — some return status, others return success, error formats vary, and pagination metadata is inconsistent. This package solves that with a single static class and an optional trait.
Before:
// Controller A return response()->json(['status' => 'ok', 'result' => $users], 200); // Controller B return response()->json(['success' => true, 'data' => $post, 'msg' => 'Done']); // Controller C return response()->json(['error' => 'Not found'], 404);
After:
return ApiResponse::success($users); return ApiResponse::created($post); return ApiResponse::notFound();
Every response follows the same structure. Every time.
Requirements
- PHP 8.1+
- Laravel 10, 11, 12, or 13
Installation
composer require haseebmirza/laravel-api-response
The service provider is auto-discovered — no manual registration needed.
Quick Start
use HaseebMirza\ApiResponse\ApiResponse; class UserController extends Controller { public function index() { return ApiResponse::success(User::all(), 'Users retrieved'); } public function store(StoreUserRequest $request) { $user = User::create($request->validated()); return ApiResponse::created($user); } public function show(User $user) { return ApiResponse::success($user); } public function destroy(User $user) { $user->delete(); return ApiResponse::noContent(); } }
Example JSON response:
{
"success": true,
"message": "Users retrieved",
"data": [
{
"id": 1,
"name": "Alice"
}
]
}
Usage
Choose The Right Place For Extra Data
Use each part of the response for a specific purpose:
data: the main business payload such as user, order, token payload, or collectionmeta: structured metadata related to the response such as pagination, API version, filters, or source- custom top-level fields: optional extra fields such as
request_id,debug,trace_id,links, or anything your frontend/client needs outsidemeta
Example:
return ApiResponse::success( ['user' => $user], 'Profile loaded', 200, ['version' => 'v1', 'source' => 'mobile'], ['request_id' => 'req_123', 'debug' => app()->isLocal()] );
Static Class
Use ApiResponse directly anywhere in your application:
use HaseebMirza\ApiResponse\ApiResponse; // Success (HTTP 200) return ApiResponse::success($data, 'Operation successful'); // Success with custom status code return ApiResponse::success($data, 'Accepted', 202); // Success with extra metadata return ApiResponse::success($data, 'OK', 200, [ 'version' => '2.1', 'request_id' => 'req_abc123', ]); // Success with custom top-level fields return ApiResponse::success($data, 'OK', 200, [ 'version' => '2.1', ], [ 'request_id' => 'req_abc123', 'debug' => true, ]); // Created (HTTP 201) return ApiResponse::created($resource); // No Content (HTTP 204, empty body) return ApiResponse::noContent(); // Error (HTTP 400) return ApiResponse::error('Invalid request'); // Not Found (HTTP 404) return ApiResponse::notFound('User not found'); // Unauthorized (HTTP 401) return ApiResponse::unauthorized('Invalid credentials'); // Forbidden (HTTP 403) return ApiResponse::forbidden(); // Validation Error (HTTP 422) return ApiResponse::validationError($validator->errors()); // Server Error (HTTP 500) return ApiResponse::serverError();
Common Real-World Snippets
1. Return a single resource
return ApiResponse::success($user, 'User fetched successfully');
2. Return a newly created resource
$post = Post::create($request->validated()); return ApiResponse::created($post);
3. Return validation errors
$validator = Validator::make($request->all(), [ 'email' => ['required', 'email'], 'password' => ['required', 'min:8'], ]); if ($validator->fails()) { return ApiResponse::validationError($validator->errors()); }
4. Return custom top-level fields
return ApiResponse::success( $order, 'Order fetched', 200, ['version' => 'v1'], ['request_id' => (string) Str::uuid(), 'trace_id' => 'trace_456'] );
5. Return an API token after login
$token = $user->createToken('api')->plainTextToken; return ApiResponse::withToken( $token, $user, 'Bearer', 'Login successful', ['source' => 'mobile'], ['request_id' => 'req_login_123'] );
6. Return an empty 204 response
return ApiResponse::noContent();
noContent() returns HTTP 204 with an empty response body. That matches HTTP semantics and avoids clients discarding a JSON payload.
Pagination
Wrap Laravel's paginator to include pagination metadata automatically:
$users = User::where('active', true)->paginate(15); return ApiResponse::paginated($users, 'Active users');
Response:
{
"success": true,
"message": "Active users",
"data": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"meta": {
"current_page": 1,
"last_page": 5,
"per_page": 15,
"total": 74,
"from": 1,
"to": 15
}
}
You can also pass extra metadata:
return ApiResponse::paginated($users, 'Filtered users', [ 'filter' => 'active', 'sort' => 'created_at', ]);
And if you need custom top-level fields outside meta:
return ApiResponse::paginated($users, 'Filtered users', [ 'filter' => 'active', ], [ 'request_id' => 'req_abc123', ]);
Authentication / Token Response
Return a token with optional user data — works with Sanctum, JWT, or any token-based auth:
// With user data $token = $user->createToken('api')->plainTextToken; return ApiResponse::withToken($token, $user); // Token only return ApiResponse::withToken($token); // Custom token type return ApiResponse::withToken($apiKey, $user, 'API-Key'); // Token response with metadata and custom top-level fields return ApiResponse::withToken($token, $user, 'Bearer', 'Authenticated successfully', [ 'request_source' => 'mobile', ], [ 'request_id' => 'req_abc123', ]);
Response:
{
"success": true,
"message": "Authenticated successfully",
"data": {
"token": "1|abc123...",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "Haseeb",
"email": "haseeb@example.com"
}
}
}
Controller Trait
Prefer $this-> syntax? Add the ApiResponder trait to your controller or a base controller:
use HaseebMirza\ApiResponse\Traits\ApiResponder; class Controller extends BaseController { use ApiResponder; }
Then use it in any controller:
class OrderController extends Controller { public function index() { $orders = Order::paginate(20); return $this->paginatedResponse($orders, 'Orders retrieved'); } public function store(StoreOrderRequest $request) { $order = Order::create($request->validated()); return $this->createdResponse($order); } public function show(Order $order) { return $this->successResponse($order); } public function update(UpdateOrderRequest $request, Order $order) { $order->update($request->validated()); return $this->successResponse($order, 'Order updated'); } public function destroy(Order $order) { $order->delete(); return $this->noContentResponse(); } }
Trait example with extra fields:
return $this->successResponse( $order, 'Order loaded', 200, ['version' => 'v1'], ['request_id' => 'req_order_123'] );
Response Format
All responses follow a consistent JSON structure:
Success:
{
"success": true,
"message": "Success",
"data": { }
}
Success with metadata:
{
"success": true,
"message": "Success",
"data": { },
"meta": { }
}
Success with custom top-level fields:
{
"success": true,
"message": "Success",
"data": { },
"meta": { },
"request_id": "req_123",
"debug": true
}
No content:
HTTP 204 with an empty body.
Error:
{
"success": false,
"message": "Error message"
}
Error with details:
{
"success": false,
"message": "Validation failed",
"errors": { }
}
API Reference
Static Methods (ApiResponse)
| Method | Status | Description |
|---|---|---|
success($data, $message, $statusCode, $meta, $additional) |
200 | General success |
created($data, $message, $meta, $additional) |
201 | Resource created |
noContent($message, $additional) |
204 | No content, empty non-JSON body |
error($message, $statusCode, $errors, $additional) |
400 | General error |
unauthorized($message, $additional) |
401 | Authentication required |
forbidden($message, $additional) |
403 | Access denied |
notFound($message, $additional) |
404 | Resource not found |
validationError($errors, $message, $additional) |
422 | Validation failed |
serverError($message, $additional) |
500 | Internal server error |
paginated($paginator, $message, $meta, $additional) |
200 | Paginated collection |
withToken($token, $user, $tokenType, $message, $meta, $additional) |
200 | Auth token response |
Reserved keys:
successmessagedatametaerrors
If a developer passes any of these inside $additional, the package ignores them so the response contract stays stable.
Trait Methods (ApiResponder)
| Trait Method | Calls |
|---|---|
$this->successResponse(...) |
ApiResponse::success() |
$this->errorResponse(...) |
ApiResponse::error() |
$this->createdResponse(...) |
ApiResponse::created() |
$this->noContentResponse(...) |
ApiResponse::noContent() |
$this->notFoundResponse(...) |
ApiResponse::notFound() |
$this->unauthorizedResponse(...) |
ApiResponse::unauthorized() |
$this->forbiddenResponse(...) |
ApiResponse::forbidden() |
$this->validationErrorResponse(...) |
ApiResponse::validationError() |
$this->serverErrorResponse(...) |
ApiResponse::serverError() |
$this->paginatedResponse(...) |
ApiResponse::paginated() |
$this->tokenResponse(...) |
ApiResponse::withToken() |
Testing
composer test
If your machine has multiple PHP versions installed, make sure you run tests with a supported version for your current dependency lock file. Example:
php83 $(which composer) test
Recommended pre-publish check:
composer test
Then install the package in a fresh Laravel app and verify a real route:
Route::get('/test-package', function () { return \HaseebMirza\ApiResponse\ApiResponse::success( ['ok' => true], 'Package working', 200, ['version' => 'v1'], ['request_id' => 'req_demo_123'] ); });
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
The MIT License (MIT). Please see LICENSE for more information.