This package is in pre-release. The API may change before v1.0.

Resources

Resources define how Eloquent models are serialized into JSON:API response objects. Each resource maps a model to its type, id, attributes, relationships, links, and meta.

Creating a Resource

Use the artisan command:

php artisan api-toolkit:make-resource ProductResource --model=Product

# For resources without an Eloquent model (e.g. timestamps, stats):
php artisan api-toolkit:make-resource TimestampResource --plain

Or create one manually:

use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
    public function attributes($model): array
    {
        return [
            'name'        => $model->name,
            'description' => $model->description,
            'price'       => $model->price,
            'created_at'  => $model->created_at->toIso8601String(),
        ];
    }
}

Static Construction

Use Resource::make() to serialize a model into a JSON:API array without going through a controller or response object. This resolves the resource from the container and calls toArray():

use App\Http\Resources\ProductResource;
use App\Models\Product;

$product = Product::find(1);
$data = ProductResource::make($product);
// Returns: ['type' => 'products', 'id' => '1', 'attributes' => [...], ...]

This is useful for embedding resource data inside jobs, events, or notifications where you do not need a full HTTP response.

Type and ID Resolution

By default, the toolkit resolves:

  • ID - Uses getKey() for Eloquent models, or the id property for plain objects. Throws a RuntimeException if no ID can be determined.
  • Type - Uses the resource's $type property if set, then the model's table name, then derives it from the class name.

Customizing per Resource

Override resolveId() on your resource for full control. This takes precedence over the global resolver:

use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
    protected string $type = 'products';

    public function resolveId($model): string
    {
        return $model->public_id;
    }
}

Customizing Globally

Register a global callback that applies to all resources that don't override resolveId():

// In a service provider boot() method
use BlueBeetle\ApiToolkit\Resources\Resource;
use Illuminate\Support\Str;

Resource::resolveIdUsing(fn ($model) => $model->uuid);
Resource::resolveTypeUsing(fn ($model) => Str::kebab(class_basename($model)));

Resolution Priority

PriorityIDType
1Resource resolveId() override$type property
2Global resolveIdUsing()Global resolveTypeUsing()
3Eloquent getKey()Model table name
4$model->id propertyClass name (snake-cased)
5Throws RuntimeExceptionEmpty string

Relationships

Define relationships by mapping names to resource classes:

use BlueBeetle\ApiToolkit\Resources\Resource;

class OrderResource extends Resource
{
    public function attributes($model): array
    {
        return [
            'total'    => $model->total,
            'status'   => $model->status,
        ];
    }

    public function relationships(): array
    {
        return [
            'customer' => CustomerResource::class,
            'items'    => OrderItemResource::class,
        ];
    }
}

When a client requests ?include=customer,items, the related models are eager-loaded and included in the included array of the response. Relationship data always appears under relationships with a type/id reference.

use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
    public function self($model): ?string
    {
        return route('api.products.show', $model);
    }

    public function links($model): array
    {
        return [
            'purchase' => route('api.products.purchase', $model),
        ];
    }

    public function meta($model): array
    {
        return [
            'stock_level' => $model->stock_count > 0 ? 'in_stock' : 'out_of_stock',
        ];
    }
}

The self() link is automatically merged with any additional links from links(). If self() returns null, no self key appears in the output.

OpenAPI Schema

Define attribute types for automatic OpenAPI spec generation. The schema() method controls how your resource's attributes appear in the generated OpenAPI document.

Using String Shorthands

use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
    public function schema(): array
    {
        return [
            'name'       => 'string',
            'quantity'   => 'integer',
            'price'      => 'number',
            'is_active'  => 'boolean',
            'tags'       => 'array',
            'settings'   => 'object',
            'birthday'   => 'date',
            'created_at' => 'datetime',
        ];
    }
}

Available shorthands:

ShorthandOpenAPI Type
string{ "type": "string" }
integer / int{ "type": "integer" }
number / float / double{ "type": "number" }
boolean / bool{ "type": "boolean" }
array{ "type": "array", "items": { "type": "string" } }
object{ "type": "object" }
date{ "type": "string", "format": "date" }
datetime{ "type": "string", "format": "date-time" }

Using Full OpenAPI Definitions

For more control, pass OpenAPI property objects directly. You can mix shorthands and full definitions:

use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
    public function schema(): array
    {
        return [
            'name'        => 'string',
            'description' => ['type' => 'string', 'nullable' => true],
            'status'      => ['type' => 'string', 'enum' => ['active', 'inactive', 'draft']],
            'price'       => ['type' => 'number', 'format' => 'float', 'minimum' => 0],
            'metadata'    => ['type' => 'object', 'additionalProperties' => true],
        ];
    }
}

Automatic Inference

If you don't define schema(), the toolkit infers types from your Eloquent model's casts:

CastOpenAPI Type
integer / intinteger
float / double / decimal / decimal:Nnumber
boolean / boolboolean
stringstring
array / json / collectionobject
datestring (format: date)
datetime / immutable_datetimestring (format: date-time)
timestampinteger
encryptedstring
Backed enum classstring or integer with enum values

Internal columns (id, password, remember_token) are automatically excluded from the generated schema.