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(),
        ];
    }
}

Type and ID Resolution

By default, the toolkit resolves:

  • ID — Uses the model's public_id attribute if it exists, otherwise falls back to the primary key
  • Type — Uses the resource's $type property if set, then the model's table name, then derives it from the class name

You can customize this globally:

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

Or set it per resource:

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

    // ...
}

Relationships

Define relationships by mapping names to resource classes:

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.

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

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:

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.