PHPackages                             vandet/laravel-api-response - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. vandet/laravel-api-response

ActiveLibrary[HTTP &amp; Networking](/categories/http)

vandet/laravel-api-response
===========================

Laravel package that enforces a consistent API response envelope — success, paginated, error, and bulk partial failure — across all services.

v1.0.0(today)00[3 PRs](https://github.com/vandet/laravel-api-response/pulls)MITPHPPHP ^8.2CI passing

Since Jun 26Pushed todayCompare

[ Source](https://github.com/vandet/laravel-api-response)[ Packagist](https://packagist.org/packages/vandet/laravel-api-response)[ Docs](https://github.com/vandet/laravel-api-response)[ RSS](/packages/vandet-laravel-api-response/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (7)Versions (5)Used By (0)

laravel-api-response
====================

[](#laravel-api-response)

[![Latest Version on Packagist](https://camo.githubusercontent.com/5bdc476d87d1035f1398f19ac6a9b53ce6aaa77514975d1a999615d34fb395d1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f76616e6465742f6c61726176656c2d6170692d726573706f6e73652e737667)](https://packagist.org/packages/vandet/laravel-api-response)[![CI](https://github.com/vandet/laravel-api-response/actions/workflows/ci.yml/badge.svg)](https://github.com/vandet/laravel-api-response/actions/workflows/ci.yml)[![PHP Version](https://camo.githubusercontent.com/dcbc8bc17dba2ffc66030335708127580eb4b643bde0e8de461a26eb4b0b941a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f76616e6465742f6c61726176656c2d6170692d726573706f6e73652e737667)](https://packagist.org/packages/vandet/laravel-api-response)[![License](https://camo.githubusercontent.com/4313524f24441b556dbe468accab5b756ef5814e9e69a29fb6641ca432249bab/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f76616e6465742f6c61726176656c2d6170692d726573706f6e73652e737667)](https://github.com/vandet/laravel-api-response/blob/main/LICENSE)

A Laravel package that enforces a consistent API response envelope — success, paginated, error, and bulk partial failure — so all services speak the same shape without hand-rolling `ResponseFactory` in each one.

---

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 10, 11, 12, or 13

---

Installation
------------

[](#installation)

### Option 1 — Composer (recommended)

[](#option-1--composer-recommended)

```
composer require vandet/laravel-api-response
```

Laravel auto-discovers the service provider — no manual registration needed.

### Option 2 — Clone the repository

[](#option-2--clone-the-repository)

Use this when you want to contribute, customise the source, or install without Packagist.

**1. Clone into your project**

```
git clone https://github.com/vandet/laravel-api-response.git packages/laravel-api-response
```

**2. Add the local path repository to your `composer.json`**

```
"repositories": [
    {
        "type": "path",
        "url": "./packages/laravel-api-response"
    }
]
```

**3. Require the package**

```
composer require vandet/laravel-api-response
```

Composer symlinks the cloned folder into `vendor/` — any changes you make to the source are reflected immediately without re-running `composer update`.

### Publish the config (optional)

[](#publish-the-config-optional)

```
php artisan vendor:publish --tag=api-response-config
```

This creates `config/api-response.php` where you can toggle exception handling per type.

---

Usage
-----

[](#usage)

### ResponseFactory

[](#responsefactory)

Import once at the top of your controller:

```
use Vandet\ApiResponse\Http\ResponseFactory;
```

#### Success — single resource

[](#success--single-resource)

```
return ResponseFactory::success($user, 'User retrieved successfully.');
```

```
{ "success": true, "message": "User retrieved successfully.", "data": { ... } }
```

#### Success — created (201)

[](#success--created-201)

```
return ResponseFactory::created($user, 'User created successfully.');
```

#### Success — accepted async job (202)

[](#success--accepted-async-job-202)

```
return ResponseFactory::accepted('Import queued successfully.');
```

#### Success — paginated collection

[](#success--paginated-collection)

Pass a Laravel `LengthAwarePaginator` directly. Call `->withQueryString()` on the paginator to preserve filter/sort params in links.

```
$users = User::paginate(20)->withQueryString();

return ResponseFactory::paginated($users, 'Users retrieved successfully.');
```

```
{
    "success": true,
    "message": "Users retrieved successfully.",
    "data": [...],
    "pagination": { "current_page": 1, "last_page": 5, "per_page": 20, "total": 82, "from": 1, "to": 20 },
    "links": { "first": "...", "last": "...", "next": "...", "prev": null }
}
```

#### Success — with reference data

[](#success--with-reference-data)

```
return ResponseFactory::withIncluded($users, [
    'roles'    => Role::all()->toArray(),
    'statuses' => Status::all()->toArray(),
], 'Users retrieved successfully.');
```

#### Delete (204 — no body)

[](#delete-204--no-body)

```
return ResponseFactory::deleted();
```

#### Validation error (422)

[](#validation-error-422)

**With FormRequest (recommended) — no manual call needed.**The exception handler catches the `ValidationException` that `FormRequest` throws automatically and converts it to the standard envelope for you.

```
// FormRequest — just type-hint it, validation + response are automatic
public function store(StoreUserRequest $request): JsonResponse
{
    $dto = UserDTO::fromRequest($request);
    // ...
}
```

```
{ "success": false, "message": "Validation failed.", "code": "VALIDATION_FAILED", "errors": { "email": ["Email is required."] } }
```

**With a manual validator** — call `ResponseFactory::validationError()` yourself:

```
$validator = Validator::make($request->all(), [
    'email' => ['required', 'email'],
]);

if ($validator->fails()) {
    return ResponseFactory::validationError($validator->errors()->toArray());
}
```

#### Not found (404)

[](#not-found-404)

```
use Vandet\ApiResponse\Constants\ErrorCodes;

return ResponseFactory::notFound(ErrorCodes::USER_NOT_FOUND, 'User not found.');
```

#### Unauthorized (401) / Forbidden (403) / Conflict (409)

[](#unauthorized-401--forbidden-403--conflict-409)

```
return ResponseFactory::unauthorized(ErrorCodes::AUTH_TOKEN_EXPIRED, 'Token has expired.');
return ResponseFactory::forbidden(ErrorCodes::AUTH_USER_FORBIDDEN, 'You do not have permission.');
return ResponseFactory::conflict(ErrorCodes::USER_EMAIL_DUPLICATE, 'Email already registered.');
```

#### Bulk partial failure (207)

[](#bulk-partial-failure-207)

```
return ResponseFactory::bulkPartialFailure([
    'created' => 1,
    'failed'  => 1,
    'items'   => [
        ['index' => 0, 'success' => true,  'id' => '550e8400-...'],
        ['index' => 1, 'success' => false, 'code' => 'USER_EMAIL_DUPLICATE', 'message' => 'Email already registered.'],
    ],
]);
```

#### Rate limited (429) / Server error (500)

[](#rate-limited-429--server-error-500)

```
return ResponseFactory::rateLimited();
return ResponseFactory::serverError('Something went wrong.');
```

---

Error Codes
-----------

[](#error-codes)

All standard error codes are available as constants:

```
use Vandet\ApiResponse\Constants\ErrorCodes;

ErrorCodes::AUTH_TOKEN_EXPIRED
ErrorCodes::USER_NOT_FOUND
ErrorCodes::VALIDATION_FAILED
ErrorCodes::RESOURCE_NOT_FOUND
ErrorCodes::SERVER_UNEXPECTED_ERROR
// ... and 35 more
```

See [`src/Constants/ErrorCodes.php`](src/Constants/ErrorCodes.php) for the full list, or refer to [`04-error-code-standard.md`](../../document/docs/api-standards/04-error-code-standard.md).

---

Exception Handler
-----------------

[](#exception-handler)

The package automatically intercepts Laravel exceptions on JSON requests and converts them to the standard envelope.

ExceptionHTTPCode`ValidationException`422`VALIDATION_FAILED``AuthenticationException`401`AUTH_TOKEN_MISSING``AuthorizationException`403`AUTH_USER_FORBIDDEN``ModelNotFoundException`404`RESOURCE_NOT_FOUND``NotFoundHttpException`404`RESOURCE_NOT_FOUND``TooManyRequestsHttpException`429`SERVER_RATE_LIMITED``Throwable` (catch-all)500`SERVER_UNEXPECTED_ERROR`Only requests with `Accept: application/json` are intercepted — web/HTML routes are unaffected.

### Disabling the exception handler

[](#disabling-the-exception-handler)

To disable all automatic exception handling:

```
// config/api-response.php
'handle_exceptions' => false,
```

To disable specific exception types:

```
'exceptions' => [
    'validation'     => true,
    'authentication' => true,
    'authorization'  => false, // handle manually
    'not_found'      => true,
    'rate_limited'   => true,
    'server_error'   => true,
],
```

### Conflict with an existing exception handler

[](#conflict-with-an-existing-exception-handler)

If your service already has custom exception handling, the package renderables take priority for matched types. To opt out of specific types (see above) and handle them yourself, use `$exceptions['type'] => false` in the config.

---

Using ResponseFactory in a Custom Exception Handler
---------------------------------------------------

[](#using-responsefactory-in-a-custom-exception-handler)

You can use `ResponseFactory` directly inside your own exception handler alongside or instead of the package's built-in renderables.

### Laravel 11 — `bootstrap/app.php`

[](#laravel-11--bootstrapappphp)

```
use Illuminate\Foundation\Configuration\Exceptions;
use Vandet\ApiResponse\Http\ResponseFactory;
use Vandet\ApiResponse\Constants\ErrorCodes;
use App\Exceptions\PaymentFailedException;
use App\Exceptions\TenantSuspendedException;

->withExceptions(function (Exceptions $exceptions) {

    // Custom domain exception
    $exceptions->renderable(function (PaymentFailedException $e, $request) {
        if ($request->expectsJson()) {
            return ResponseFactory::conflict(
                ErrorCodes::PAYMENT_FAILED,
                $e->getMessage()
            );
        }
    });

    // Another domain exception
    $exceptions->renderable(function (TenantSuspendedException $e, $request) {
        if ($request->expectsJson()) {
            return ResponseFactory::forbidden(
                ErrorCodes::TENANT_SUSPENDED,
                'This account has been suspended.'
            );
        }
    });

})
```

### Laravel 10 — `app/Exceptions/Handler.php`

[](#laravel-10--appexceptionshandlerphp)

```
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Vandet\ApiResponse\Http\ResponseFactory;
use Vandet\ApiResponse\Constants\ErrorCodes;
use App\Exceptions\PaymentFailedException;
use App\Exceptions\TenantSuspendedException;

class Handler extends ExceptionHandler
{
    public function register(): void
    {
        $this->renderable(function (PaymentFailedException $e, Request $request) {
            if ($request->expectsJson()) {
                return ResponseFactory::conflict(
                    ErrorCodes::PAYMENT_FAILED,
                    $e->getMessage()
                );
            }
        });

        $this->renderable(function (TenantSuspendedException $e, Request $request) {
            if ($request->expectsJson()) {
                return ResponseFactory::forbidden(
                    ErrorCodes::TENANT_SUSPENDED,
                    'This account has been suspended.'
                );
            }
        });
    }
}
```

### Custom domain exception pattern

[](#custom-domain-exception-pattern)

Define your exception with a built-in error code so the handler stays clean:

```
class PaymentFailedException extends \RuntimeException
{
    public function __construct(string $message = 'Payment gateway rejected the transaction.')
    {
        parent::__construct($message);
    }
}
```

Then throw it anywhere in your application:

```
throw new PaymentFailedException('Card declined.');
```

The handler catches it and returns:

```
{ "success": false, "message": "Card declined.", "code": "PAYMENT_FAILED", "errors": {} }
```

---

### ApiException — built-in base class

[](#apiexception--built-in-base-class)

The package ships with `ApiException`, a base class your domain exceptions can extend. It stores the error code and HTTP status directly on the exception — no renderable registration needed.

```
use Vandet\ApiResponse\Exceptions\ApiException;
use Vandet\ApiResponse\Constants\ErrorCodes;

class PaymentFailedException extends ApiException
{
    public function __construct(string $message = 'Payment gateway rejected the transaction.')
    {
        parent::__construct(ErrorCodes::PAYMENT_FAILED, $message, 422);
    }
}

class TenantSuspendedException extends ApiException
{
    public function __construct()
    {
        parent::__construct(ErrorCodes::TENANT_SUSPENDED, 'This account has been suspended.', 403);
    }
}
```

Throw from anywhere — controller, action, service — and the package handler responds automatically:

```
// In an Action or Service
if ($tenant->isSuspended()) {
    throw new TenantSuspendedException();
}

// In a controller
throw new PaymentFailedException('Card declined.');
```

```
{ "success": false, "message": "Card declined.", "code": "PAYMENT_FAILED", "errors": {} }
```

No need to register a `renderable()` for each exception type. All classes that extend `ApiException` are caught by the service provider automatically.

#### Generic one-off errors without a custom class

[](#generic-one-off-errors-without-a-custom-class)

Use `ResponseFactory::error()` when you need a specific code and status without creating a dedicated exception class:

```
use Vandet\ApiResponse\Http\ResponseFactory;
use Vandet\ApiResponse\Constants\ErrorCodes;

return ResponseFactory::error(ErrorCodes::ORDER_CANCELLED, 'Order has been cancelled.', 409);
```

### Tip — disable the built-in handler for types you own

[](#tip--disable-the-built-in-handler-for-types-you-own)

If your service handles its own `ModelNotFoundException` with a domain-specific message, disable the package's version in `config/api-response.php` to avoid conflicts:

```
'exceptions' => [
    'not_found' => false, // I handle this myself
],
```

---

Response Envelope Reference
---------------------------

[](#response-envelope-reference)

```
// Success
{ "success": true,  "message": "...", "data": {} }
{ "success": true,  "message": "...", "data": [], "pagination": {}, "links": {} }
{ "success": true,  "message": "...", "data": [], "included": {} }

// Error
{ "success": false, "message": "...", "code": "DOMAIN_ENTITY_REASON", "errors": {} }

// Delete
HTTP 204 No Content

// Bulk partial (only error response that includes data)
{ "success": false, "message": "...", "code": "BULK_PARTIAL_FAILURE", "data": { "items": [] }, "errors": {} }

```

Optional fields (`pagination`, `links`, `included`, `meta`) are **omitted entirely** when absent — never `null`.

---

Running Tests
-------------

[](#running-tests)

```
composer install
./vendor/bin/phpunit
```

---

Changelog
---------

[](#changelog)

VersionDateChange1.0.02026-06-26Initial release

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4952333?v=4)[s-vandet](/maintainers/vandet)[@vandet](https://github.com/vandet)

---

Top Contributors

[![vandet](https://avatars.githubusercontent.com/u/4952333?v=4)](https://github.com/vandet "vandet (6 commits)")

---

Tags

httpresponsejsonapilaravelrestenvelope

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/vandet-laravel-api-response/health.svg)

```
[![Health](https://phpackages.com/badges/vandet-laravel-api-response/health.svg)](https://phpackages.com/packages/vandet-laravel-api-response)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[api-platform/laravel

API Platform support for Laravel

59156.3k11](/packages/api-platform-laravel)[rap2hpoutre/jacky

Opinionated REST JSON HTTP API client for laravel

174.4k](/packages/rap2hpoutre-jacky)[laragear/api-manager

Manage multiple REST servers to make requests in few lines and fluently.

162.0k](/packages/laragear-api-manager)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
