PHPackages                             yzen.dev/laravel-json-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. [API Development](/categories/api)
4. /
5. yzen.dev/laravel-json-api-response

ActiveLibrary[API Development](/categories/api)

yzen.dev/laravel-json-api-response
==================================

Helpers for building JSON:API responses with responder classes

1.0.0(3w ago)00MITPHPPHP ^8.0CI failing

Since May 15Pushed 3w agoCompare

[ Source](https://github.com/yzen-dev/laravel-json-api-response)[ Packagist](https://packagist.org/packages/yzen.dev/laravel-json-api-response)[ RSS](/packages/yzendev-laravel-json-api-response/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (4)Versions (2)Used By (0)

JsonApiResponse
---------------

[](#jsonapiresponse)

[![Packagist Version](https://camo.githubusercontent.com/3f7a876053ad45f6c0e796f007590750988685c4a56ca127cad99f84f503afd0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f797a656e2e6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e73653f636f6c6f723d626c7565266c6162656c3d76657273696f6e)](https://camo.githubusercontent.com/3f7a876053ad45f6c0e796f007590750988685c4a56ca127cad99f84f503afd0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f797a656e2e6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e73653f636f6c6f723d626c7565266c6162656c3d76657273696f6e)[![GitHub Workflow Status](https://camo.githubusercontent.com/7f52c562c6d566570ebbff42021b5760789639da280975a3d31b86f8c0f44008/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f797a656e2d6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e73652f74657374732e796d6c)](https://camo.githubusercontent.com/7f52c562c6d566570ebbff42021b5760789639da280975a3d31b86f8c0f44008/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f797a656e2d6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e73652f74657374732e796d6c)[![Coverage](https://camo.githubusercontent.com/3fe537f03a5988b6dc422a972ef389ff673e03bd39fd444df785a884824c6abd/68747470733a2f2f636f6465636f762e696f2f67682f797a656e2d6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e73652f67726170682f62616467652e737667)](https://codecov.io/gh/yzen-dev/laravel-json-api-response)[![License](https://camo.githubusercontent.com/10ff388fed839adeb4f41deb28f3239b8695365302dfed12addeba54b6f2ad20/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f797a656e2d6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e7365)](https://camo.githubusercontent.com/10ff388fed839adeb4f41deb28f3239b8695365302dfed12addeba54b6f2ad20/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f797a656e2d6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e7365)[![Packagist Downloads](https://camo.githubusercontent.com/b53b3c1cb2cb610e93260d16b30d145c633c04daa91c771bee80f13ec1626e0f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f797a656e2e6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e7365)](https://camo.githubusercontent.com/b53b3c1cb2cb610e93260d16b30d145c633c04daa91c771bee80f13ec1626e0f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646d2f797a656e2e6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e7365)[![Packagist Downloads](https://camo.githubusercontent.com/4df3cecf54e46edda86361345b544934d9295d6bbc2061c0784488f9a172c64e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f797a656e2e6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e7365)](https://camo.githubusercontent.com/4df3cecf54e46edda86361345b544934d9295d6bbc2061c0784488f9a172c64e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f797a656e2e6465762f6c61726176656c2d6a736f6e2d6170692d726573706f6e7365)[![PHPStan](https://camo.githubusercontent.com/44dc5f71fec76653887c975fe3db546a82ff603d094798eb6414a38369db1f44/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d6c6576656c253230382d627269676874677265656e)](https://camo.githubusercontent.com/44dc5f71fec76653887c975fe3db546a82ff603d094798eb6414a38369db1f44/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068707374616e2d6c6576656c253230382d627269676874677265656e)

`JsonApiResponse` helps you build JSON:API-compatible response payloads with small responder classes.

The package is framework-light: the responder logic itself works with plain PHP objects and arrays, while HTTP responses and paginated lists use Laravel components:

- `illuminate/http`
- `illuminate/pagination`

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

[](#installation)

```
composer require yzen.dev/json-api-response
```

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

[](#requirements)

- PHP `^8.0`
- `illuminate/http`
- `illuminate/pagination`

Basic Responder
---------------

[](#basic-responder)

Create a responder by extending `AbstractResponder` and implementing two methods:

```
use YzendDev\Laravel\JsonApiResponse\Responder\AbstractResponder;

/**
 * @extends AbstractResponder
 */
final class UserResponder extends AbstractResponder
{
    protected function resourceType(): string
    {
        return 'User';
    }

    protected function composeAttributes(mixed $entity): array
    {
        return [
            'name' => $entity->name,
            'isActive' => $entity->is_active,
            'createdAt' => $entity->created_at->toIso8601String(),
        ];
    }
}
```

Then compose a resource document:

```
$response = (new UserResponder())->compose($user);
```

The resulting payload follows the standard JSON:API shape:

```
[
    'data' => [
        'id' => 10,
        'type' => 'User',
        'attributes' => [
            'name' => 'yzen.dev',
            'isActive' => true,
            'createdAt' => '2026-05-15T12:00:00+00:00',
        ],
    ],
]
```

By default, `resourceId()` returns `$entity->id`. Override it if your entity uses a different identifier.

Relationships
-------------

[](#relationships)

Define relationships in `relationships()` and pass the related records through `withRelationData()`.

```
use YzendDev\Laravel\JsonApiResponse\Responder\RelationshipDefinition;

protected function relationships(): array
{
    return [
        'countryFrom' => new RelationshipDefinition('country_from_id', CountryResponder::class),
        'cityFrom' => new RelationshipDefinition('city_from_id', CityResponder::class),
    ];
}
```

The relation data must be passed as a map keyed by the foreign key value:

```
$countries = [
    10 => $country,
    11 => $anotherCountry,
];

$responder = (new UserResponder())
    ->withRelationData('countryFrom', $countries);
```

If the foreign key is `null`, or the related object is missing from the map, the relationship value becomes `null`.

To-Many Relationships
---------------------

[](#to-many-relationships)

Use `ToManyRelationshipDefinition` for grouped child resources:

```
use YzendDev\Laravel\JsonApiResponse\Responder\ToManyRelationshipDefinition;

protected function relationships(): array
{
    return [
        'pickups' => new ToManyRelationshipDefinition('id', PickupResponder::class),
    ];
}
```

Pass grouped data keyed by the parent entity field declared in `entityKey`:

```
$pickups = [
    5 => [
        $pickupA,
        $pickupB,
    ],
    6 => [
        $pickupC,
    ],
];

$responder = (new UserResponder())
    ->withRelationData('pickups', $pickups);
```

Nested Relationship Data
------------------------

[](#nested-relationship-data)

If a child responder has its own relationships, pass that data through `withNestedRelationData()`:

```
$responder = (new UserResponder())
    ->withRelationData('pickups', $pickups)
    ->withNestedRelationData('pickups', 'city', [
        7 => $hamburg,
        8 => $berlin,
    ]);
```

This is useful when a to-many relation needs nested linked resources without loading them inside the responder itself.

Includes
--------

[](#includes)

Top-level `included` resources are defined through `includes()`:

```
use YzendDev\Laravel\JsonApiResponse\Responder\IncludeDefinition;

protected function includes(): array
{
    return [
        'units' => new IncludeDefinition(UnitResponder::class),
    ];
}
```

Pass included entities as a flat array:

```
$responder = (new SomeResponder())->withIncludeData('units', [
    $unitA,
    $unitB,
]);
```

Paginated Lists
---------------

[](#paginated-lists)

Use `composeList()` with Laravel's `LengthAwarePaginator`:

```
$payload = (new UserResponder())->composeList($paginator);
```

The package adds pagination metadata automatically:

```
[
    'meta' => [
        'totalRecords' => 50,
        'perPage' => 15,
        'currentPage' => 2,
        'lastPage' => 4,
    ],
]
```

HTTP Response Wrapper
---------------------

[](#http-response-wrapper)

`YzendDev\Laravel\JsonApiResponse\Responder\JsonResponse` extends Laravel's `JsonResponse` and sets JSON:API headers:

```
use YzendDev\Laravel\JsonApiResponse\Responder\JsonResponse;

return new JsonResponse($payload);
```

In a controller, a typical response looks like this:

```
use YzendDev\Laravel\JsonApiResponse\Responder\JsonResponse;

return new JsonResponse(
    status: JsonResponse::HTTP_OK,
    data: new UserResponder()->compose($request->user()),
);
```

Headers set by default:

- `Content-type: application/vnd.api+json`
- `Charset: utf-8`

Error Responses
---------------

[](#error-responses)

### Validation Errors

[](#validation-errors)

```
use YzendDev\Laravel\JsonApiResponse\Responder\ValidationErrorsResponder;

$errors = (new ValidationErrorsResponder())->compose([
    'email' => ['The email field is required.'],
]);
```

### Ready-to-use JSON:API Error Documents

[](#ready-to-use-jsonapi-error-documents)

The package also ships a few ready-made JSON response classes:

- `AuthenticationJsonApiException`
- `AuthorizationJsonApiException`
- `ResourceConflictJsonApiException`
- `ResourceNotFoundJsonApiException`
- `ErpSystemJsonApiException`

Global Exception Handling
-------------------------

[](#global-exception-handling)

In Laravel, the package works best when JSON:API error rendering is configured once in `bootstrap/app.php`.

```
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use YzendDev\Laravel\JsonApiResponse\Responder\JsonResponse;
use YzendDev\Laravel\JsonApiResponse\Responder\Types\AuthenticationJsonApiException;
use YzendDev\Laravel\JsonApiResponse\Responder\Types\AuthorizationJsonApiException;
use YzendDev\Laravel\JsonApiResponse\Responder\Types\ResourceConflictJsonApiException;
use YzendDev\Laravel\JsonApiResponse\Responder\Types\ResourceNotFoundJsonApiException;
use YzendDev\Laravel\JsonApiResponse\Responder\ValidationErrorsResponder;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions): void {
        $exceptions->render(fn (AuthenticationException $e) => new AuthenticationJsonApiException());
        $exceptions->render(fn (AuthorizationException $e) => new AuthorizationJsonApiException());
        $exceptions->render(fn (AccessDeniedException $e) => new AuthorizationJsonApiException());

        $exceptions->render(function (ValidationException $e) {
            return new JsonResponse(
                status: JsonResponse::HTTP_UNPROCESSABLE_ENTITY,
                data: [
                    'errors' => (new ValidationErrorsResponder())->compose($e->errors()),
                ],
            );
        });

        $exceptions->render(fn (MethodNotAllowedHttpException $e) => new JsonResponse(status: 405));
        $exceptions->render(fn (ResourceNotFoundException $e) => new ResourceNotFoundJsonApiException($e));
        $exceptions->render(fn (NotFoundHttpException $e) => new ResourceNotFoundJsonApiException($e));
        $exceptions->render(fn (ModelNotFoundException $e) => new ResourceNotFoundJsonApiException($e));
        $exceptions->render(fn (FileNotFoundException $e) => new ResourceNotFoundJsonApiException($e));
        $exceptions->render(fn (ResourceConflictException $e) => new ResourceConflictJsonApiException($e));
    })
    ->create();
```

With this approach, application code can throw domain-level exceptions and let Laravel convert them into JSON:API error documents in one place:

```
if (! $user) {
    throw new ResourceNotFoundException('User was not found.');
}
```

That keeps controllers and services focused on business logic while response formatting stays centralized.

Notes
-----

[](#notes)

- Responders are intentionally passive. They format already loaded data and do not fetch dependencies themselves.
- Relationship maps and grouped child arrays should be prepared before calling the responder.
- The public usage model in this package follows the examples from `responder.md`.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance94

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 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

25d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8ca07942b4d955ee72b1bc92220b1bc7686708d0f109ee4abf81f8fbd30732c3?d=identicon)[yzen.dev](/maintainers/yzen.dev)

---

Top Contributors

[![yzen-dev](https://avatars.githubusercontent.com/u/24630195?v=4)](https://github.com/yzen-dev "yzen-dev (2 commits)")

---

Tags

responsephplaraveljsonapiJSON-APIresponder

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

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

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

###  Alternatives

[nuwave/lighthouse

A framework for serving GraphQL from Laravel

3.5k11.4M110](/packages/nuwave-lighthouse)[resend/resend-laravel

Resend for Laravel

1212.2M8](/packages/resend-resend-laravel)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[laravel-json-api/core

Contracts and support classes for Laravel JSON:API packages.

101.6M16](/packages/laravel-json-api-core)[dystcz/lunar-api

Dystore API layer for Lunar e-commerce package

411.2k3](/packages/dystcz-lunar-api)

PHPackages © 2026

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