API Resources


Overview

The Lyger\Api namespace provides tools to build consistent, well-structured JSON API responses. This includes resource transformers, JSON:API-compatible wrappers, and standardized response helpers.


ApiResponse (Static Helpers)

The quickest way to return consistent API responses:

use Lyger\Api\ApiResponse;

// Success
return Response::json(ApiResponse::success($data, 'Users retrieved'));

// Error
return Response::json(ApiResponse::error('Not found', 404), 404);

// Validation error
return Response::json(ApiResponse::validationError($validator->errors()), 422);

Standard Response Shapes

Success:

{
  "success": true,
  "message": "Users retrieved",
  "data": [...]
}

Error:

{
  "success": false,
  "message": "Not found",
  "errors": null
}

Validation Error:

{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "email": ["The email field is required."],
    "name": ["The name must be at least 2 characters."]
  }
}

All ApiResponse Methods

// Basic
ApiResponse::success(mixed $data, string $message = 'Success', int $code = 200): array
ApiResponse::error(string $message, int $code = 500, ?array $errors = null): array

// Convenience shortcuts
ApiResponse::notFound(): array                    // 404
ApiResponse::unauthorized(): array                // 401
ApiResponse::forbidden(): array                   // 403
ApiResponse::validationError(array $errors): array // 422

// Resource operations
ApiResponse::created(mixed $data): array          // 201 Created
ApiResponse::deleted(): array                     // 200 + "deleted" message

// Pagination
ApiResponse::paginated(
    array $data,
    int $page,
    int $perPage,
    int $total
): array

Paginated response shape:

{
  "success": true,
  "data": [...],
  "meta": {
    "current_page": 2,
    "per_page": 15,
    "total": 120,
    "last_page": 8
  }
}

Resource Transformers

Use Resource to transform model data and decouple your API shape from your database schema:

<?php

namespace App\Resources;

use Lyger\Api\Resource;

class UserResource extends Resource
{
    public function toArray(): array
    {
        return [
            'id'         => $this->data['id'],
            'name'       => $this->data['name'],
            'email'      => $this->data['email'],
            'created_at' => $this->data['created_at'],
            // Omit: password, internal IDs, sensitive fields
        ];
    }
}

Use in a controller:

$user     = User::find(1);
$resource = new UserResource($user->toArray());

return Response::json(ApiResponse::success($resource->toArray()));

Collection of Resources

$users     = User::all()->toArray();
$resources = UserResource::collection($users);

return Response::json(ApiResponse::success($resources));

JSON:API Resource (JsonResource)

For JSON:API specification compliance, use JsonResource:

use Lyger\Api\JsonResource;

$resource = new JsonResource($data);

// Add metadata
$resource->meta(['version' => '1.0', 'generated_at' => time()]);

// Add links
$resource->link('self', '/api/users/1');
$resource->link('related', '/api/users/1/posts');

return Response::json($resource->toArray());

Output shape:

{
  "data": { "id": 1, "name": "Alice" },
  "meta": { "version": "1.0" },
  "links": { "self": "/api/users/1" }
}

ApiController Base Class

Extend ApiController to get helper methods built in:

<?php

namespace App\Controllers;

use Lyger\Api\ApiController;
use Lyger\Http\Request;
use Lyger\Http\Response;
use App\Models\Post;

class PostApiController extends ApiController
{
    public function index(Request $request): Response
    {
        $posts = Post::query()
            ->where('published', true)
            ->paginate(15, (int) $request->get('page', 1));

        return Response::json(
            $this->paginated($posts['data'], $posts['current_page'], 15, $posts['total'])
        );
    }

    public function show(Request $request, int $id): Response
    {
        $post = Post::find($id);
        if (!$post) {
            return Response::json($this->notFound(), 404);
        }
        return Response::json($this->success($post->toArray()));
    }

    public function store(Request $request): Response
    {
        // ... validate ...
        $post = Post::create($request->all());
        return Response::json($this->created($post->toArray()), 201);
    }

    public function destroy(Request $request, int $id): Response
    {
        $post = Post::findOrFail($id);
        $post->delete();
        return Response::json($this->deleted());
    }
}

Available methods via ApiController:

Method Description
success($data, $message, $code) Wrap in success shape
error($message, $code, $errors) Wrap in error shape
notFound() 404 response array
unauthorized() 401 response array
forbidden() 403 response array
validationError($errors) 422 response array
created($data) 201 Created response array
deleted() Deleted response array
paginated($data, $page, $perPage, $total) Paginated response array

// routes/web.php (API Headless architecture)
use App\Controllers\UserApiController;
use App\Controllers\PostApiController;

// Users
Route::get('/api/v1/users', [UserApiController::class, 'index']);
Route::post('/api/v1/users', [UserApiController::class, 'store']);
Route::get('/api/v1/users/{id}', [UserApiController::class, 'show']);
Route::put('/api/v1/users/{id}', [UserApiController::class, 'update']);
Route::delete('/api/v1/users/{id}', [UserApiController::class, 'destroy']);

// Posts
Route::get('/api/v1/posts', [PostApiController::class, 'index']);
Route::post('/api/v1/posts', [PostApiController::class, 'store']);
Route::get('/api/v1/posts/{id}', [PostApiController::class, 'show']);
Route::put('/api/v1/posts/{id}', [PostApiController::class, 'update']);
Route::delete('/api/v1/posts/{id}', [PostApiController::class, 'destroy']);

Copyright © 2026 Lyger Framework. Distributed under the MIT License.

This site uses Just the Docs, a documentation theme for Jekyll.