A comprehensive Laravel package for universal API response and error handling. This package provides a standardized way to format API responses and handle exceptions in Laravel applications with multiple usage patterns and advanced features.
- π― Standardized API Responses - Consistent response format across your entire API
- π‘οΈ Automatic Exception Handling - Custom exception handler for API routes
- π Multiple Usage Methods - Use as service, trait, facade, macros, or helper functions
- βοΈ Highly Configurable - Customize response structure and messages
- π Built-in Pagination - Automatic pagination response formatting
- π§ Laravel 9-12 Support - Compatible with multiple Laravel versions
- π― Resource Classes - Built-in resource transformation classes
- π Response Macros - Extend Laravel's response with API methods
- π Global Helpers - Easy-to-use global helper functions
- π οΈ Middleware Support - Automatic response formatting middleware
- π¦ Rate Limiting Integration - Formatted rate limiting responses
- π¦ Response Compression - Automatic response compression (gzip, deflate)
- π API Versioning - Built-in API versioning support
- π Performance Monitoring - Response performance tracking
- PHP >= 8.1
- Laravel >= 9.0
composer require anyo-lab/api-response- Clone or download this package
- Add to your
composer.json:
{
"require": {
"anyo-lab/api-response": "*"
},
"repositories": [
{
"type": "path",
"url": "path/to/api-response"
}
]
}- Run
composer update
Publish the configuration file:
php artisan vendor:publish --provider="ApiResponse\ApiResponseServiceProvider" --tag="api-response-config"This will create config/api-response.php with customizable options.
Before using in a live project:
- Set
APP_DEBUG=falsein.envso error responses never expose stack traces or file paths. - Publish config (optional):
php artisan vendor:publish --tag=api-response-configto override messages, structure, or timestamp format. - Override messages in
config/api-response.phpundermessagesso all default strings (e.g. "Resource not found") come from one place. - Optional: Enable
include_request_idandinclude_error_codesin config for tracing and machine-readable error codes.
All responses use the same config (structure, timestamp, error codes). ResponseHelper and resources use the same settings as the main ApiResponse service.
-
Response caching removed: The package no longer provides
api_cache(),api_remember(), orApiResponseCache. For cached API responses, use LaravelβsCache::remember()withapi_success():use Illuminate\Support\Facades\Cache; $data = Cache::remember('users.list', 3600, fn () => User::all()); return api_success($data, 'Success');
-
204 No Content:
noContent()now returns an empty response body (RFC 7231) instead of a JSON body. -
Performance monitoring optional: Set
register_performance_monitoringtofalsein config to disable cache usage when you do not useapi_performance().
See CHANGELOG.md for full version history.
use ApiResponse\ApiResponse;
class UserController extends Controller
{
public function index(ApiResponse $apiResponse)
{
$users = User::paginate(10);
return $apiResponse->paginated($users, 'Users retrieved successfully');
}
public function store(Request $request, ApiResponse $apiResponse)
{
$user = User::create($request->validated());
return $apiResponse->created($user, 'User created successfully');
}
}Tip: For testability and flexibility, type-hint ApiResponseContract instead of ApiResponse so you can swap or mock the implementation.
use ApiResponse\ApiResponse;
class UserController extends Controller
{
public function index(ApiResponse $apiResponse)
{
$users = User::paginate(10);
return $apiResponse->paginated($users, 'Users retrieved successfully');
}
public function store(Request $request, ApiResponse $apiResponse)
{
$user = User::create($request->validated());
return $apiResponse->created($user, 'User created successfully');
}
}use ApiResponse\Traits\ApiResponseTrait;
class UserController extends Controller
{
use ApiResponseTrait;
public function index()
{
$users = User::paginate(10);
return $this->paginatedResponse($users, 'Users retrieved successfully');
}
public function store(Request $request)
{
$user = User::create($request->validated());
return $this->createdResponse($user, 'User created successfully');
}
}use ApiResponse\Facades\ApiResponse;
class UserController extends Controller
{
public function index()
{
$users = User::paginate(10);
return ApiResponse::paginated($users, 'Users retrieved successfully');
}
public function store(Request $request)
{
$user = User::create($request->validated());
return ApiResponse::created($user, 'User created successfully');
}
}class UserController extends Controller
{
public function index()
{
$users = User::paginate(10);
return response()->apiPaginated($users, 'Users retrieved successfully');
}
public function store(Request $request)
{
$user = User::create($request->validated());
return response()->apiCreated($user, 'User created successfully');
}
}class UserController extends Controller
{
public function index()
{
$users = User::paginate(10);
return api_paginated($users, 'Users retrieved successfully');
}
public function store(Request $request)
{
$user = User::create($request->validated());
return api_created($user, 'User created successfully');
}
}use ApiResponse\Resources\ApiResource;
use Illuminate\Http\Request;
class UserResource extends ApiResource
{
protected function transformData(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at?->toISOString(),
];
}
protected function getMessage(): string
{
return 'User retrieved successfully';
}
}
class UserController extends Controller
{
public function show($id)
{
$user = User::findOrFail($id);
return new UserResource($user);
}
public function index()
{
$users = User::paginate(10);
return UserResource::collection($users);
}
}// Basic success response
$apiResponse->success($data, 'Success message');
// Created response (201)
$apiResponse->created($data, 'Resource created');
// No content response (204)
$apiResponse->noContent('No content available');
// Paginated response
$apiResponse->paginated($paginator, 'Data retrieved');
// Collection response
$apiResponse->collection($collection, 'Data retrieved');
// Resource response
$apiResponse->resource($resource, 'Resource retrieved');// Basic error response
$apiResponse->error('Error message', 400);
// Validation error (422)
$apiResponse->validationError($errors, 'Validation failed');
// Not found (404)
$apiResponse->notFound('Resource not found');
// Unauthorized (401)
$apiResponse->unauthorized('Authentication required');
// Forbidden (403)
$apiResponse->forbidden('Access denied');
// Server error (500)
$apiResponse->serverError('Internal server error');
// Bad request (400)
$apiResponse->badRequest('Invalid request');
// Conflict (409)
$apiResponse->conflict('Resource conflict');
// Too many requests (429)
$apiResponse->tooManyRequests('Rate limit exceeded');All responses follow this standardized format:
{
"success": true,
"message": "Success message",
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"errors": null,
"meta": null,
"timestamp": "2024-01-01T12:00:00.000000Z"
}{
"success": false,
"message": "Error message",
"data": null,
"errors": {
"email": ["The email field is required."],
"password": ["The password must be at least 8 characters."]
},
"meta": null,
"timestamp": "2024-01-01T12:00:00.000000Z"
}{
"success": true,
"message": "Data retrieved successfully",
"data": [
{
"id": 1,
"name": "John Doe"
}
],
"errors": null,
"meta": {
"current_page": 1,
"per_page": 10,
"total": 100,
"last_page": 10,
"from": 1,
"to": 10,
"has_more_pages": true
},
"timestamp": "2024-01-01T12:00:00.000000Z"
}The package includes a custom exception handler that automatically formats exceptions for API routes:
| Exception | Status Code | Description |
|---|---|---|
ValidationException |
422 | Validation errors |
AuthenticationException |
401 | Unauthorized |
ModelNotFoundException |
404 | Not found |
NotFoundHttpException |
404 | Not found |
QueryException |
500 | Server error (with debug info in development) |
HttpException |
Respective | HTTP status code |
'structure' => [
'success' => 'success',
'message' => 'message',
'data' => 'data',
'errors' => 'errors',
'meta' => 'meta',
'timestamp' => 'timestamp',
],'messages' => [
'success' => 'Success',
'created' => 'Resource created successfully',
'updated' => 'Resource updated successfully',
'deleted' => 'Resource deleted successfully',
'not_found' => 'Resource not found',
'unauthorized' => 'Unauthorized',
'forbidden' => 'Forbidden',
'validation_failed' => 'Validation failed',
'server_error' => 'Internal server error',
'bad_request' => 'Bad request',
'conflict' => 'Conflict occurred',
'too_many_requests' => 'Too many requests',
],'include_timestamp' => true,
'timestamp_format' => 'Y-m-d H:i:s',
'debug' => env('APP_DEBUG', false),
'use_exception_handler' => true,
'api_routes_pattern' => 'api/*',// In your routes/api.php
Route::middleware(['api.response'])->group(function () {
Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store']);
});
// Or register globally in App\Http\Kernel.php
protected $middlewareGroups = [
'api' => [
\ApiResponse\Http\Middleware\ApiResponseMiddleware::class,
// ... other middleware
],
];use ApiResponse\Services\ApiVersioningService;
class UserController extends Controller
{
public function index(ApiVersioningService $versioning)
{
$version = $versioning->getVersion(request());
if ($version === 'v2') {
// Return v2 format
return $this->paginatedResponseV2($users);
}
// Return v1 format
return $this->paginatedResponse($users);
}
}use ApiResponse\Services\ResponseCompressionService;
class UserController extends Controller
{
public function index(ResponseCompressionService $compression)
{
$response = $this->paginatedResponse($users);
return $compression->compress(request(), $response);
}
}use ApiResponse\Services\PerformanceMonitoringService;
class UserController extends Controller
{
public function index(PerformanceMonitoringService $monitoring)
{
$monitoring->startMonitoring(request());
$users = User::paginate(10);
$response = $this->paginatedResponse($users);
$monitoring->endMonitoring(request(), $response);
return $response;
}
}<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
class UserApiTest extends TestCase
{
public function test_can_get_users()
{
$response = $this->getJson('/api/users');
$response->assertStatus(200)
->assertJsonStructure([
'success',
'message',
'data',
'meta',
'timestamp'
])
->assertJson([
'success' => true,
'message' => 'Users retrieved successfully'
]);
}
public function test_can_create_user()
{
$userData = [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123'
];
$response = $this->postJson('/api/users', $userData);
$response->assertStatus(201)
->assertJson([
'success' => true,
'message' => 'User created successfully'
]);
}
public function test_validation_errors()
{
$response = $this->postJson('/api/users', []);
$response->assertStatus(422)
->assertJson([
'success' => false,
'message' => 'Validation failed'
])
->assertJsonStructure([
'errors' => [
'name',
'email',
'password'
]
]);
}
}<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use ApiResponse\Traits\ApiResponseTrait;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class UserController extends Controller
{
use ApiResponseTrait;
public function index()
{
try {
$users = User::paginate(10);
return $this->paginatedResponse($users, 'Users retrieved successfully');
} catch (\Exception $e) {
return $this->serverErrorResponse('Failed to retrieve users');
}
}
public function store(Request $request)
{
try {
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8',
]);
$user = User::create($validated);
return $this->createdResponse($user, 'User created successfully');
} catch (ValidationException $e) {
return $this->validationErrorResponse($e->errors(), 'Validation failed');
} catch (\Exception $e) {
return $this->serverErrorResponse('Failed to create user');
}
}
public function show($id)
{
try {
$user = User::findOrFail($id);
return $this->resourceResponse($user, 'User retrieved successfully');
} catch (ModelNotFoundException $e) {
return $this->notFoundResponse('User not found');
}
}
public function update(Request $request, $id)
{
try {
$user = User::findOrFail($id);
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'email' => 'sometimes|email|unique:users,email,' . $id,
]);
$user->update($validated);
return $this->successResponse($user, 'User updated successfully');
} catch (ModelNotFoundException $e) {
return $this->notFoundResponse('User not found');
} catch (ValidationException $e) {
return $this->validationErrorResponse($e->errors(), 'Validation failed');
}
}
public function destroy($id)
{
try {
$user = User::findOrFail($id);
$user->delete();
return $this->successResponse(null, 'User deleted successfully');
} catch (ModelNotFoundException $e) {
return $this->notFoundResponse('User not found');
}
}
}composer install
composer test # run PHPUnit
composer format:check # Laravel Pint
composer analyse # PHPStanFor production use, enable request IDs and error codes in config; apply rate limiting and set APP_DEBUG=false.
Please see CONTRIBUTING.md for development setup, code style, and pull request guidelines.
If you discover a security vulnerability, please see SECURITY.md.
See CHANGELOG.md for version history.
This package is open-sourced software licensed under the MIT license.
- Issues: GitHub Issues
- Documentation: Full Documentation
- Laravel team for the amazing framework
- All contributors who help improve this package
- The open-source community for inspiration and feedback
Laravel API Response β A generic package for standardized API responses.