Back to Course |
How to Build Laravel 11 API From Scratch

Generating API Documentation with Scribe

In this lesson, we will talk about API documentation and how to generate it. This lesson will use Scribe to auto-generate documentation. At the end, we will have a similar page to the image below:


We can install Scribe via composer with the --dev flag because this package is for a development environment. And then publish the config file.

composer require --dev knuckleswtf/scribe
php artisan vendor:publish --tag=scribe-config

By default, Scribe is set to use all the routes under the /api prefix. This setting is set in the config/scribe.php under the routes.match.prefixes key.

Now, we can generate the documentation using an artisan command.

php artisan scribe:generate

After running the generate command in the terminal, we see that the package went through all routes and generated docs in the public/docs directory.

Now, you can visit /docs in the browser and should see the generated documentation.

After installing the package and running the generate command, we made an API documentation page. Also, for example, for the api/categories endpoint as an example response, we have real data taken from a database. When generating, Scribe goes through all the endpoints and tries to launch them, and the response is shown in the example.

This is how you can generate API documentation for yourself or the frontender.


Now, let's look at some of the customizations that can be done. First, we can configure an introduction text and logo.

config/scribe.php:

<?php
 
use Knuckles\Scribe\Extracting\Strategies;
 
return [
// ...
 
'intro_text' => <<<INTRO
This documentation aims to provide all the information you need to work with our API. // [tl! --]
This documentation aims to provide all the information you need to work with our E-Shop API. // [tl! ++]
 
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
INTRO
,
 
// ...
 
'logo' => '',
'logo' => 'https://demo-eshop-bootstrap.filamentexamples.com/storage/logo.png',
 
// ...
];

After every change, you need to regenerate.

php artisan scribe:generate

Let's rename endpoints from api/categories to Get Categories.

We can rename it in two ways. The first one uses docblock, and the second uses PHP attributes in the Controller.

The docblock version:

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

class CategoryController extends Controller
{
/**
* Get Categories
*
* Getting the list of the categories
*/
public function index()
{
return CategoryResource::collection(Category::all());
}
 
// ...
}

The PHP attributes version:

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

use Knuckles\Scribe\Attributes\Endpoint;
 
class CategoryController extends Controller
{
#[Endpoint('Get Categories', <<<DESC
Getting the list of the categories
DESC)]
public function index()
{
return CategoryResource::collection(Category::all());
}
 
// ...
}

Next, we can group endpoints into menu items. This can also be done using docblock and PHP attributes.

Docblock version:

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

 
/**
* @group Categories
*
* Managing Categories
*/
class CategoryController extends Controller
{
// ...
}

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

 
/**
* @group Products
*
* Managing Products
*/
class ProductController extends Controller
{
// ...
}

PHP attributes version:

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

use Knuckles\Scribe\Attributes\Group;
 
#[Group('Categories', 'Managing Categories')]
class CategoryController extends Controller
{
// ...
}

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

use Knuckles\Scribe\Attributes\Group;
 
#[Group('Products', 'Managing Products')]
class ProductController extends Controller
{
// ...
}

Now we have categories and products under their menu item, and everything else is under the endpoints.

URLs often have query parameters—for example, a page parameter.

The docblock version:

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

class CategoryController extends Controller
{
/**
* Get Categories
*
* Getting the list of the categories
*
* @queryParam page Which page to show. Example: 12
*/
public function index()
{
return CategoryResource::collection(Category::all());
}
 
// ...
}

The PHP attributes version:

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

use Knuckles\Scribe\Attributes\QueryParam;
 
class CategoryController extends Controller
{
#[Endpoint('Get Categories', <<<DESC
Getting the list of the categories
DESC)]
#[QueryParam('page', 'int', 'Which page to show.', example: 12)]
public function index()
{
return CategoryResource::collection(Category::all());
}
 
// ...
}

It also adds a parameter to the example request.

Now, for the POST request, let's add body parameters.

Docblock version:

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

class CategoryController extends Controller
{
// ...
 
/**
* POST categories
*
* @bodyParam name string required Name of the category. Example: "Clothing
*/
public function store(StoreCategoryRequest $request)
{
// ...
}
 
// ...
}

PHP attributes version:

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

class CategoryController extends Controller
{
// ...
 
#[BodyParam('name', 'string', 'Name of the category.', true, 'Clothing')]
public function store(StoreCategoryRequest $request)
{
// ...
}
 
// ...
}

As in the query parameters example, the name is now required instead of optional.

For more, check the official Scribe documentation.