we'll be using JSON, both in the response and in the request body
06.2 Web APIs Introduction
Before we start: Tools & JSON
Use a Web API testing tool such as Postman or Insomnia
The Accept: application/json header is very important to Laravel: if you don't indicate you expect JSON, Laravel will send you back HTML pages
The Content-Type: application/json header is required when your request contains JSON in the body. If you don't set it, Laravel won't be able to parse the JSON and your $request data will be empty
Web API routing
Want to offer a stateless API?
Enable API routing and API authentication by php artisan install:api*
Put all your Web API routes in routes/api.php**
The routes in this file are assigned the prefix /api/** !!!
Web API communication is assumed to be stateless, so the middlewares applied to routes/api.php are different from those applied to routes/web.php
(there is no session control, cookie encryption nor CRSF protection, but there is API throttling in stead)
You actually ± know how to program Web API endpoints:
just continue using Controllers, Validation, Eloquent, …
When the product exists, the Eloquent model object $product will automatically be serialized into JSON ($product->toJson()) and converted into a Response object
(with Content-Type: application/json)
When the product doesn't exist, findOrFail() launches a NotFoundHttpException resulting in a JSON*-like 404 response
Demo v1 with Postman
use App\Models\Product;
use Illuminate\Database\Eloquent\Collection;
Route::get('/v1/products/{id}', function (string $id): Product {
$product = Product::findOrFail($id);
return $product;
})->where('id', '[0-9]+');
Route::get('/v1/products', function (): Collection {
return Product::all();
});
Route::get('/v2/products/{id}', function (string $id): array {
$product = Product::with('brand')->findOrFail($id);
return ['data' => $product];
})->where('id', '[0-9]+');
Route::get('/v2/products', function (): array {
return ['data' => Product::with('brand')->get()];
});
Route::post('/v2/products', function (Request $request): array {
$request->validate([
'name' => 'required|unique:products|max:125',
'price' => 'required|numeric|min:0.10',
'description' => 'required',
'brand_id' => 'required|exists:brands,id'
]);
$product = new Product($request->all());
$product->save();
return ['message' => 'The product has been created'];
});
Exercise
In almost any endpoint, when a book is returned as JSON, the book should contain
the fields id, isbn and title (these are columns in the DB)
the field internal_code (which is a concatenation of the book's id and location columns)
the related author's JSON
Which 4 things should be added to the model class Book?
In 1 endpoint, the book should contain the description column in stead of isbn. How can this be overridden?
06.4 Eloquent API Resources
Introduction
In certain situations, defining one single JSON response per Eloquent model is not satisfactory
Eloquent API Resources form a transformation layer between your Eloquent models and JSON responses
You will define Resource classes, defining a transformation between an Eloquent model and a JSON response
Creating a Resource
How to create such a transformation?
php artisan make:resource ProductResource
⟶ a resource class is created in app/Http/Resources
⟶ in toArray($request), include all attributes for this transformation
class ProductResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'price' => $this->price,
'brand' => new BrandResource($this->brand),
'categories' => CategoryResource::collection($this->categories),
];
}
}
Creating a Resource Collection
You don't need to if you have a regular collection!
How to create a transformation for collections of models?
php artisan make:resource ProductCollection
class name ending on Collection or add the flag --collection
⟶ a resource collection class is created in app/Http/Resources
⟶ in toArray($request), define how you want your collection to be transformed
class ProductCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
Basic Usage
Example in your routes/api.php
use App\Http\Resources\ProductResource;
use App\Models\Product;
Route::get('/v3/products/{id}', function (string $id): ProductResource {
return new ProductResource(Product::findOrFail($id));
});
Route::get('/v3/products', function (): ProductCollection {
return new ProductCollection(Product::all());
// or, if you don't have a ProductCollection:
// return ProductResource::collection(Product::all());
});
By default, all data is wrapped in a data key (configurable)
When passing a paginated result, the response contains meta and links keys describing the paginator's state