Skip to content

Instantly share code, notes, and snippets.

@dehsilvadeveloper
Last active January 9, 2025 12:11
Show Gist options
  • Save dehsilvadeveloper/4118735621b0d979cb8bf664f3668f00 to your computer and use it in GitHub Desktop.
Save dehsilvadeveloper/4118735621b0d979cb8bf664f3668f00 to your computer and use it in GitHub Desktop.
Including API documentation on Laravel 10 application using package SCRIBE

Including API documentation on Laravel 10 application using package SCRIBE

Incluindo documentação de API numa aplicação Laravel 10 utilizando package Scribe

Introduction

Scribe helps you generate API documentation for humans from your Laravel codebase.

You can learn more about the package here: https://scribe.knuckles.wtf/laravel/

We will see how to implement it.

Step 1: Instalation

Installing package by running on terminal:

composer require --dev knuckleswtf/scribe

The package will be installed as development dependency.

Step 2: Publishing config file

Publish the config file by running on terminal:

php artisan vendor:publish --tag=scribe-config

This will create a scribe.php file in your config folder.

Step 3: Configuring the documentation

Defining documentation type

The type key present on the Scribe config file tells the type of docs setup you want. There are two options: static (that generates a simple HTML file) and laravel (that generates a blade view served via Laravel routes). For our scenario we will configure using the laravel type.

[config/scribe.php]

'type' => 'laravel',

Defining API basic info

You can set some basic information about the API, like the title, description, introdution text and base url. You can do this by editing the keys of config array as the example that follows.

[config/scribe.php]

  'title' => 'Book API',

  'description' => 'API that allows the basic CRUD operation for books.',

  'intro_text' => <<<INTRO
This documentation will provide all the information you need to work with our API.

<aside>
As you scroll, you'll see code examples for working with the API in the available 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,

  'base_url' => '{{config("app.url")}}:{{config("app.port")}}',

Example requests

You can define the programming languages that will be used to show examples of requests to the endpoints of the API. The examples will be shown in each of these languages defined in the config array in the area on the right of the page.

[config/scribe.php]

'example_languages' => [
    'bash',
    'javascript',
    'php',
    'python'
],

Postman and OpenAPI collections

If you will not use Postman or OpenAPI collections exporting, you can disable them on the config array.

[config/scribe.php]

'postman' => [
    'enabled' => false,
],

'openapi' => [
    'enabled' => false,
],

Enabling authentication

You can enable authentication for the API documentation following this example:

'auth' => [
    'enabled' => true,
],

Defining API documentation endpoint

You can define the endpoint where you can see the API documentation following this example:

'laravel' => [
    'docs_url' => '/api-docs',
],

After that, you can access the API documentation on the following url:

http://localhost:9999/api-docs

Remember that the base url and port are based on your application environment variables, so it can differ from what is showed here.

Step 4: Do a test run

Now, let's do a test run. Run the command to generate your docs.

php artisan scribe:generate

After this, make sure that your application is running and access the API documentaton endpoint.

http://localhost:9999/api-docs

You can navigate through the documentation.

From now on, for any changes that you make that affect the documentation somehow, you will have to run the generation code again, so keep that in mind.

php artisan scribe:generate

Step 5: Implement Scribe annotations

Now you have to implement the Scribe annotations on the dockblocks of your controllers and requests classes as needed.

An example for a controller:

<?php

namespace App\Http\Controllers\Api;

use Throwable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use App\Domain\Auth\DataTransferObjects\LoginDto;
use App\Domain\Auth\Exceptions\IncorrectPasswordException;
use App\Domain\Auth\Exceptions\InvalidUserException;
use App\Domain\Auth\Services\Interfaces\AuthServiceInterface;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\LoginRequest;
use App\Http\Resources\AuthenticatedUserResource;
use App\Traits\Http\ApiResponses;

/**
 * @group Authentication
 *
 * Endpoints for managing API authentication
 */
class AuthController extends Controller
{
    use ApiResponses;

    public function __construct(private AuthServiceInterface $authService)
    {
    }

    /**
     * Login
     *
     * This endpoint lets you login an API user, generating an access token for him.
     *
     * @responseField access_token string The access token that will be used to authenticate API requests.
     * @responseField token_type string The type of token generated.
     * @responseField expires_at string The date and time in which the token will expire.
     *
     * @response status=200 scenario=success {
     *      "data": {
     *          "access_token": "1|laravel10_falemaisUI8A7aHrlN0XCyKApJCfO2uzK9Gc4X8DWZtFJbCY4d735783",
     *          "token_type": "Bearer",
     *          "expires_at": "2024-02-01 12:27:37"
     *      }
     * }
     *
     * @response status=400 scenario="user with email provided not found" {
     *      "message": "Could not found a valid user with the email: [email protected]."
     * }
     *
     * @response status=400 scenario="password incorrect" {
     *      "message": "The password provided for this user is incorrect."
     * }
     *
     * @response status=500 scenario="unexpected error" {
     *      "message": "Internal Server Error."
     * }
     *
     */
    public function login(LoginRequest $request): JsonResponse
    {
        try {
            $dto = LoginDto::from([
                'email' => $request->input('email'),
                'password' => $request->input('password')
            ]);

            $result = $this->authService->login($dto);

            return $this->sendSuccessResponse(
                data: $result->toArray(),
                code: Response::HTTP_OK
            );
        } catch (Throwable $exception) {
            Log::error(
                '[AuthController] Failed to login with the credentials provided.',
                [
                    'error_message' => $exception->getMessage(),
                    'file' => $exception->getFile(),
                    'line' => $exception->getLine(),
                    'data' => [
                        'received_data' => $request->all() ?? null
                    ],
                    'stack_trace' => $exception->getTrace()
                ]
            );

            $exceptionTypes = [InvalidUserException::class, IncorrectPasswordException::class];

            $errorMessage = in_array(get_class($exception), $exceptionTypes)
                ? $exception->getMessage()
                : 'An error has occurred. Could not login with the credentials provided as requested.';

            return $this->sendErrorResponse(
                message: $errorMessage,
                code: $exception->getCode()
            );
        }
    }
}

An example for a request class (in this case we do not use annotations, but include a method bodyParameters):

<?php

namespace App\Http\Requests\Api;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'email' => ['required', 'string', 'min:6', 'max:70', 'email'],
            'password' => ['required', 'string', 'min:6']
        ];
    }

    /**
     * Define body params to be used by the API documentation
     */
    public function bodyParameters(): array
    {
        return [
            'email' => [
                'description' => 'The e-mail of the user.',
                'example' => '[email protected]'
            ],
            'password' => [
                'description' => 'The password of the user.',
                'example' => 'LfMJvB5b9xZbF76Q4tFT'
            ]
        ];
    }
}

You can refer to the Scribe documentation (https://scribe.knuckles.wtf/laravel/) to know more about the available annotations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment