Courses

How to Build Laravel 11 API From Scratch

Getting Single Record and API Resources

Let's continue building category API; the following endpoint will be for the individual category.


First, let's add a route.

routes/api.php:

Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
 
Route::get('categories', [\App\Http\Controllers\Api\CategoryController::class, 'index']);
Route::get('categories/{category}', [\App\Http\Controllers\Api\CategoryController::class, 'show']);

We will get to resource routes later.

Next, in the controller we add a show method which will return the category object.

app/Http/Controllers/Api/CategoryController.php:

class CategoryController extends Controller
{
public function index()
{
return Category::all();
}
 
public function show(Category $category)
{
return $category;
}
}

Now, we can launch this API endpoint in the client.

But how do you show only the name? For this, it's time to introduce the API Resources.


API Resources

We can create API Resources using an artisan command.

php artisan make:resource CategoryResource

It is generated automatically in the App/Http/Resources directory.

In the create Resource, we add all the fields we want to be shown.

app/Http/Resources/CategoryResource.php:

class CategoryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
return parent::toArray($request);
}
}

$this is the record.

Instead of returning an object in the Controller, we return a new Resource with a category as a parameter.

app/Http/Controllers/CategoryController.php:

use App\Http\Resources\CategoryResource;
 
class CategoryController extends Controller
{
public function index()
{
return Category::all();
}
 
public function show(Category $category)
{
return $category;
return new CategoryResource($category);
}
}

After re-visiting the endpoint, we see only two fields are showing. Also, we have a new layer of data. This is a best practice of JSON API to return data within the data, and everything else is reserved for error messages or any additional information.

Now, let's use the Resource in the index method, but instead of a new Resource, we will use a collection.

use App\Http\Resources\CategoryResource;
 
class CategoryController extends Controller
{
public function index()
{
return Category::all();
return CategoryResource::collection(Category::all());
}
 
public function show(Category $category)
{
return new CategoryResource($category);
}
}

After relaunching the /categories endpoint, we see the result is in the same data.

This is how you introduce rules of what fields must be returned.


API Resources: Different Fields for Endpoints?

For example, we have another API route with the endpoint of /lists/categories, which doesn't need to show a description. The description must only be shown for routes with the /categories endpoint.

In the CategoryResource, we would add description to the array.

app/Http/Resources/CategoryResource.php:

class CategoryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
];
}
}

As for the additional Route:

routes/api.php:

Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
 
Route::get('lists/categories', [\App\Http\Controllers\Api\CategoryController::class, 'list'])
 
Route::get('categories', [\App\Http\Controllers\Api\CategoryController::class, 'index']);
Route::get('categories/{category}', [\App\Http\Controllers\Api\CategoryController::class, 'show']);

app/Http/Controllers/Api/CategoryController.php:

class CategoryController extends Controller
{
// ...
 
public function list()
{
return CategoryResource::collection(Category::all());
}
}

Now, how do you show a description only when it is needed?

In the Resource, we have a Request as an accepted parameter to use everything from the request; in this case, we can check for the Route. The check for the field can be done using conditional attribute when.

app/Http/Resources/CategoryResource.php:

class CategoryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->when($request->is('api/categories*'), function () {
return $this->description;
}),
];
}
}

If we check the /categories endpoint, the description is shown.

But for the endpoint /lists/categories, the category is hidden.

Next, what if you want to show a description limited to the whole list, and when showing a single category, the description should be full-length?

We can add a simple if inside when closure.

app/Http/Resources/CategoryResource.php:

class CategoryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->when($request->is('api/categories*'), function () use ($request) {
if ($request->is('api/categories')) {
return str($this->description)->limit(20);
};
 
return $this->description;
}),
];
}
}

When viewing a single category, we see that the description is full-length.

But when viewing all categories, the list description is limited to 20 characters.

But, in general, I suggest creating different API Resources for every method. For example, you could create a Resource called CategoryShowResource for a single category and add a description without limits.

Then, for showing a list of categories, the Resource could be CategoryIndexResource where the description length is limited or is not even shown.

And, as you can see here, we give Resource names similar to Controller methods. If your application has many Resources, they can be moved to their separate directories.

This way, in my opinion, Resources will be much more readable and easier to maintain.

avatar
Francisco Ferreira Roque Júnior

For those who were unable to view the description information, remember that it is necessary to create a new migration adding the description field to the categories table.

Schema::table('categories', function (Blueprint $table) { $table->string('description'); });

👍 3
avatar

It's only for a demonstration, that's why we didn't add this field.

avatar
Francisco Ferreira Roque Júnior

oh ok, got you, thanks

avatar

My descriptions didn't show for a single category /api/categories/1 so I needed to put an if/else otherwise it just returned a null.

        'description' => $this->when($request->is('api/categories*'), function () use ($request) {
            if ($request->is('api/categories') ) {
                return str($this->description)->limit(20);
            } else {
                return $this->description;
            }
avatar
Briere Mostafa Amine

As mentioned in the first comment that you'll have to add the field description in categories migration.