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

Responses

The toolkit provides two response types: SuccessResponse for data and ErrorResponse for errors. Both implement Laravel's Responsable interface, so you can return them directly from controllers.

Success Responses

Single Resource

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);
    }
}

Returns:

{
  "data": {
    "type": "products",
    "id": "1",
    "attributes": {
      "name": "Widget",
      "price": 29.99
    }
  }
}

Collections

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

class ProductController
{
    public function index(Response $response)
    {
        $products = Product::all();

        return $response->success($products, ProductResource::class);
    }
}
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' => $requestId])
            ->links(['self' => route('api.products.show', $product)]);
    }
}

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:

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

use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\Http\Response;
use Illuminate\Http\Request;

class ProductController
{
    public function store(Request $request, Response $response)
    {
        $product = Product::create($request->validated());

        return $response->success($product, ProductResource::class)
            ->respond(201, ['X-Custom-Header' => 'value']);
    }
}

Raw Array Data

For responses that don't map to a resource, pass the data directly:

use BlueBeetle\ApiToolkit\Http\Response;

class HealthController
{
    public function __invoke(Response $response)
    {
        return $response->success([
            'message' => 'Import complete',
            'rows_processed' => 42,
        ]);
    }
}

No Content (204)

For delete operations or actions that return no body:

use App\Models\Product;
use BlueBeetle\ApiToolkit\Http\Response;

class ProductController
{
    public function destroy(Product $product, Response $response)
    {
        $product->delete();

        return $response->noContent();
    }
}

Returns a 204 No Content response with an empty body and the application/vnd.api+json content type.

Error Responses

Basic Error

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

class ProductController
{
    public function show(string $id, Response $response)
    {
        $product = Product::find($id);

        if ($product === null) {
            return $response->error('Not Found', 'The requested product does not exist.', 404);
        }

        return $response->success($product, ProductResource::class);
    }
}

Returns:

{
  "errors": [
    {
      "status": "404",
      "title": "Not Found",
      "detail": "The requested product does not exist."
    }
  ]
}

With Code and Source

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

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:

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

The QueryBuilder returns SuccessResponse instances directly:

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

class ProductController
{
    public function index(Request $request)
    {
        return QueryBuilder::for(Product::class, $request)
            ->fromResource(ProductResource::class)
            ->paginate();     // offset pagination
    }

    public function all(Request $request)
    {
        return QueryBuilder::for(Product::class, $request)
            ->fromResource(ProductResource::class)
            ->get();          // all results, no pagination
    }
}