laravel-api-response maintained by haseebmirza
Laravel API Response
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.