laravel-json-api/neomerx-json-api
Installation
composer require laravel-json-api/neomerx-json-api
Add to composer.json:
"autoload": {
"psr-4": {
"App\\JsonApi\\": "app/JsonApi/"
}
}
Run composer dump-autoload.
Basic Configuration Publish the config file:
php artisan vendor:publish --provider="Neomerx\JsonApi\JsonApiServiceProvider" --tag="config"
Update config/json-api.php with your API root (e.g., /api).
First Use Case: Resource Definition
Define a resource in app/JsonApi/Resources/UserResource.php:
namespace App\JsonApi\Resources;
use Neomerx\JsonApi\Resource\Resource;
use Neomerx\JsonApi\Resource\ResourceCollection;
use App\Models\User;
class UserResource extends Resource
{
public static function fields(): array
{
return [
'id',
'name',
'email',
'posts' => function () {
return new PostResource();
}
];
}
}
First Controller
use Neomerx\JsonApi\JsonApiResource;
use App\JsonApi\Resources\UserResource;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return JsonApiResource::collection(UserResource::class, $users);
}
}
Route Definition
use Illuminate\Support\Facades\Route;
Route::jsonApiApi(['prefix' => 'api']);
Nested Resources
Define relationships in fields():
'posts' => function () {
return new PostResource();
}
Use belongsTo, hasMany, etc., for eager loading:
public static function relations(): array
{
return [
'posts' => function () {
return Post::query()->with('comments');
}
];
}
Custom Field Transformations Use closures or methods:
'full_name' => function (User $user) {
return $user->first_name . ' ' . $user->last_name;
}
Pagination
Use JsonApiResource::collection() with paginate():
return JsonApiResource::collection(
UserResource::class,
User::paginate(10)
);
Filtering
Use JsonApiResource::collection() with query parameters:
$users = User::query()
->when(request('filter[name]', false), fn($q, $name) => $q->where('name', 'like', "%{$name}%"))
->get();
Sorting
Parse sort query param:
$sort = request('sort', 'id');
$users = User::query()->orderBy($sort)->get();
Sparse Fieldsets Dynamically include/exclude fields:
$fields = request('fields[user]', ['id', 'name']);
$resource = new UserResource(User::find(1), $fields);
API Routes
Use Route::jsonApiApi() for automatic route generation:
Route::jsonApiApi(['prefix' => 'api', 'version' => 'v1']);
Middleware Apply middleware to JSON API routes:
Route::jsonApiApi(['middleware' => 'auth:api']);
Form Requests
Validate JSON API requests with JsonApiRequest:
use Neomerx\JsonApi\JsonApiRequest;
class StoreUserRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'data' => 'required|array',
'data.type' => 'required|string',
'data.attributes' => 'sometimes|array',
];
}
}
Documentation
Use JsonApiResource::document() for meta/data:
return JsonApiResource::document([
'data' => UserResource::collection($users),
'meta' => ['version' => '1.0']
]);
Case Sensitivity in Type Fields
Ensure data.type matches the resource class name (e.g., User not user).
Fix: Use Resource::type() to enforce consistency:
class UserResource extends Resource
{
public static function type(): string
{
return 'users'; // lowercase plural
}
}
Missing id in Resources
JSON API requires id in every resource object.
Fix: Ensure your model has a primary key ($primaryKey = 'id').
Circular References Avoid infinite loops in nested resources:
// ❌ Avoid
'author' => function () { return new UserResource(); }
// ✅ Fix: Use lazy loading
'author' => function () { return new UserResource(); },
public static function relations(): array
{
return ['author' => fn() => User::with('posts')->find($this->id)];
}
Pagination Headers
JSON API requires pagination metadata. Use JsonApiResource::collection() with paginate():
// ❌ Missing pagination headers
return JsonApiResource::collection(UserResource::class, User::all());
// ✅ Correct
return JsonApiResource::collection(UserResource::class, User::paginate(10));
Enable JSON API Debugging
Add to config/json-api.php:
'debug' => env('JSON_API_DEBUG', false),
Logs will appear in storage/logs/laravel.json-api.log.
Validate Requests
Use JsonApiRequest to validate incoming data:
$request = new JsonApiRequest();
$request->replace(request()->all());
if (!$request->isValid()) {
return response()->json(['errors' => $request->errors()], 400);
}
Inspect Resource Output
Use toArray() to debug resource serialization:
$resource = new UserResource(User::find(1));
dd($resource->toArray());
Custom Error Handling
Override JsonApiServiceProvider:
public function register()
{
$this->app->singleton(JsonApiExceptionHandler::class, function () {
return new CustomJsonApiExceptionHandler();
});
}
Custom Serializers
Extend Resource for custom logic:
class CustomUserResource extends UserResource
{
public function toArray($request = null)
{
$array = parent::toArray($request);
$array['attributes']['custom_field'] = 'value';
return $array;
}
}
Middleware for JSON API
Add to app/Http/Kernel.php:
protected $middlewareGroups = [
'json-api' => [
\Neomerx\JsonApi\JsonApiMiddleware::class,
// Other middleware...
],
];
Testing
Use JsonApiTestCase for assertions:
use Neomerx\JsonApi\Testing\JsonApiTestCase;
class UserTest extends JsonApiTestCase
{
public function test_user_serialization()
{
$response = $this->getJson('/api/users/1');
$response->assertJsonApiResource(UserResource::class);
}
}
Performance Optimization
with() in relations() to avoid N+1 queries.$resource = Cache::remember("user.{$user->id}", now()->addHours(1), function () use ($user) {
return new UserResource($user);
});
How can I help you explore Laravel packages today?