m-hidayatullahh / laravel-api-kit
Standardized API response envelope and global exception handling for Laravel 10 & 11.
Package info
github.com/m-hidayatullahh/laravel-api-kit
pkg:composer/m-hidayatullahh/laravel-api-kit
Requires
- php: ^8.1
- illuminate/contracts: ^10.0 || ^11.0
- illuminate/http: ^10.0 || ^11.0
- illuminate/support: ^10.0 || ^11.0
Requires (Dev)
- orchestra/testbench: ^8.20 || ^9.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-laravel: ^2.4
README
Standardized API response envelope + global exception handling for Laravel 10 & 11, PHP 8.1+.
One install gives every backend project the same success/error/validation/pagination shape, so the frontend writes one set of handlers instead of many.
Install
Private repo via composer.json:
{
"repositories": [
{ "type": "vcs", "url": "git@github.com:company/laravel-api-kit.git" }
]
}
composer require company/laravel-api-kit php artisan vendor:publish --tag=api-kit-config # optional php artisan vendor:publish --tag=api-kit-lang # optional
The ServiceProvider and ApiResponse facade auto-register (package discovery).
Response shapes
Success:
{ "success": true, "message": "Data berhasil diambil", "data": {} }
Validation error (422):
{ "success": false, "message": "Validation failed", "errors": { "email": ["Email wajib diisi"] } }
Server error (500): { "success": false, "message": "Terjadi kesalahan pada server" }
Unauthorized (401): { "success": false, "message": "Unauthorized" }
Not found (404): { "success": false, "message": "Data tidak ditemukan" }
Usage
use Company\ApiKit\Facades\ApiResponse; return ApiResponse::success($user, 'Data berhasil diambil'); return ApiResponse::error('Gagal memproses', 400); return ApiResponse::validation(['email' => ['Email wajib diisi']]); return ApiResponse::unauthorized(); return ApiResponse::forbidden(); return ApiResponse::notFound(); return ApiResponse::paginate(User::paginate());
Or via the controller trait:
use Company\ApiKit\Traits\HasApiResponse; class UserController extends Controller { use HasApiResponse; public function index() { return $this->respondPaginated(User::paginate()); } }
Throwing for business rules (no try/catch needed — the renderer formats it):
use Company\ApiKit\Exceptions\BusinessException; throw new BusinessException('Saldo tidak mencukupi', status: 422);
Wiring global exception handling
Laravel 11 — bootstrap/app.php
use Company\ApiKit\ApiKit; ->withExceptions(function (Illuminate\Foundation\Configuration\Exceptions $exceptions) { ApiKit::handleExceptions($exceptions); })
Laravel 10 — app/Exceptions/Handler.php
use Company\ApiKit\Exceptions\Concerns\InteractsWithApiExceptions; class Handler extends ExceptionHandler { use InteractsWithApiExceptions; public function register(): void { $this->renderApiExceptions(); } }
Configuration highlights
api_prefix— which routes get standardized JSON errors (defaultapi).include_code— add a machine-readablecode(e.g.VALIDATION_FAILED) to error bodies.keys— rename envelope keys for incremental legacy migration.logging— 5xx are logged by default; 4xx are not.- Messages live in publishable lang files (
id,en). Setapp.localeor edit after publish.
Testing
composer install
composer test
Built with Pest + Orchestra Testbench.