Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions docs/1.basics/1.resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ class ProductResource extends Resource
}
```

## 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()`:

```php
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:
Expand All @@ -49,6 +64,8 @@ By default, the toolkit resolves:
Override `resolveId()` on your resource for full control. This takes precedence over the global resolver:

```php
use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
protected string $type = 'products';
Expand Down Expand Up @@ -144,7 +161,7 @@ The `self()` link is automatically merged with any additional links from `links(

## 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.
Define attribute types for automatic [OpenAPI spec generation](/docs/api-toolkit/latest/advanced/openapi). The `schema()` method controls how your resource's attributes appear in the generated OpenAPI document.

### Using String Shorthands

Expand Down Expand Up @@ -187,15 +204,20 @@ Available shorthands:
For more control, pass OpenAPI property objects directly. You can mix shorthands and full definitions:

```php
public function schema(): array
use BlueBeetle\ApiToolkit\Resources\Resource;

class ProductResource extends Resource
{
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],
];
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],
];
}
}
```

Expand Down
67 changes: 62 additions & 5 deletions docs/1.basics/2.responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ class ProductController
}
```

### Merging Meta and Links

Calling `meta()` or `links()` multiple times merges the values rather than replacing them. This is useful when different parts of your code need to add their own metadata:

```php
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\Http\Response;

class ProductController
{
public function show(Product $product, Response $response)
{
return $response->success($product, ProductResource::class)
->meta(['request_id' => 'req_abc123'])
->meta(['cache_hit' => false]) // merged with previous meta
->links(['self' => route('api.products.show', $product)])
->links(['related' => route('api.categories.show', $product->category_id)]); // merged
}
}
```

### Custom Status Code and Headers

```php
Expand Down Expand Up @@ -177,16 +199,51 @@ Returns:
### With Code and Source

```php
return $response->error('Validation Failed', 'The name field is required.', 422)
->code('VALIDATION_ERROR')
->source(['pointer' => '/data/attributes/name']);
use BlueBeetle\ApiToolkit\Http\Response;

class ProductController
{
public function update(string $id, Response $response)
{
return $response->error('Validation Failed', 'The name field is required.', 422)
->code('VALIDATION_ERROR')
->source(['pointer' => '/data/attributes/name']);
}
}
```

### With Meta

```php
return $response->error('Rate Limited', 'Too many requests.', 429)
->meta(['retry_after' => 60]);
use BlueBeetle\ApiToolkit\Http\Response;

class RateLimitedController
{
public function __invoke(Response $response)
{
return $response->error('Rate Limited', 'Too many requests.', 429)
->meta(['retry_after' => 60]);
}
}
```

### Chaining Code, Source, and Meta

All ErrorResponse methods are chainable, so you can combine them in a single expression:

```php
use BlueBeetle\ApiToolkit\Http\Response;

class PaymentController
{
public function charge(Response $response)
{
return $response->error('Payment Failed', 'Card was declined.', 402)
->code('CARD_DECLINED')
->source(['pointer' => '/data/attributes/card_number'])
->meta(['decline_code' => 'insufficient_funds', 'retry_allowed' => true]);
}
}
```

## Using with QueryBuilder
Expand Down
62 changes: 60 additions & 2 deletions docs/1.basics/3.query-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Then in your controller, `fromResource()` picks up all of these automatically.

## Overriding Resource Configuration

Method calls take priority over resource definitions:
Method calls on the QueryBuilder take priority over resource definitions. If you call `allowedFilters()`, `allowedSorts()`, `defaultSort()`, or `allowedIncludes()` on the builder, those values replace whatever the resource defines. This lets you reuse the same resource class across endpoints while customizing behavior per route.

```php
use App\Http\Resources\ProductResource;
Expand Down Expand Up @@ -135,7 +135,9 @@ class ProductController

## Apply Without Fetching

Use `apply()` to apply filters, sorts, and includes without executing the query. This is useful when you need the modified query for something other than a standard response, like counting or exporting:
Use `apply()` to apply filters, sorts, and includes without executing the query. This returns the QueryBuilder instance, so you can call `getQuery()` to access the underlying Eloquent builder. This is useful when you need the modified query for something other than a standard response.

### Counting Results

```php
use App\Http\Resources\ProductResource;
Expand All @@ -157,3 +159,59 @@ class ProductController
}
}
```

### Exporting to CSV

```php
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;

class ProductExportController
{
public function __invoke(Request $request)
{
$query = QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->apply()
->getQuery();

return response()->streamDownload(function () use ($query) {
$query->each(function ($product) {
echo $product->name . ',' . $product->price . "\n";
});
}, 'products.csv');
}
}
```

### Custom Response Format

```php
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;

class ProductStatsController
{
public function __invoke(Request $request)
{
$query = QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->apply()
->getQuery();

return response()->json([
'total' => $query->count(),
'avg_price' => $query->avg('price'),
'max_price' => $query->max('price'),
]);
}
}
```

## The getQuery() Method

`getQuery()` returns the underlying `Illuminate\Database\Eloquent\Builder` instance. You can call it at any time, but it is most useful after `apply()` so the builder already has all filters, sorts, and includes applied. The returned builder is a standard Eloquent builder, so you can chain any Eloquent method on it.
Loading