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 theidproperty for plain objects. Throws aRuntimeExceptionif no ID can be determined. - Type - Uses the resource's
$typeproperty 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
| Priority | ID | Type |
|---|---|---|
| 1 | Resource resolveId() override | $type property |
| 2 | Global resolveIdUsing() | Global resolveTypeUsing() |
| 3 | Eloquent getKey() | Model table name |
| 4 | $model->id property | Class name (snake-cased) |
| 5 | Throws RuntimeException | Empty 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.
Links and Meta
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:
| Shorthand | OpenAPI 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:
| Cast | OpenAPI Type |
|---|---|
integer / int | integer |
float / double / decimal / decimal:N | number |
boolean / bool | boolean |
string | string |
array / json / collection | object |
date | string (format: date) |
datetime / immutable_datetime | string (format: date-time) |
timestamp | integer |
encrypted | string |
| Backed enum class | string or integer with enum values |
Internal columns (id, password, remember_token) are automatically excluded from the generated schema.