Documentació d'API

Com documentar la teva API amb Scribe, Swagger/OpenAPI i bones pràctiques de documentació.

Per què documentar l'API?#

Una API sense documentació és com una biblioteca sense catàleg: pot tenir tots els llibres del món, però ningú pot trobar el que busca. La documentació explica quins endpoints estan disponibles, quins paràmetres accepten, quin format tenen les respostes, com s'autentica una petició i quins errors pot retornar cada endpoint. Sense ella, els consumidors de l'API han d'endevinar el comportament, llegir el codi font o fer proves de prova i error.

La documentació és especialment crítica quan l'API la consumeixen equips o desenvolupadors externs que no tenen accés al codi. Però fins i tot per a equips interns, una documentació clara redueix la comunicació necessària ("quin format espera aquest endpoint?") i serveix com a referència ràpida durant el desenvolupament del frontend o les integracions.

El repte de la documentació és mantenir-la actualitzada. Una documentació desactualitzada pot ser pitjor que no tenir-ne, perquè enganya els clients amb informació incorrecta. Per això, les millors eines generen la documentació directament a partir del codi: si el codi canvia, la documentació es regenera automàticament.

Laravel no inclou eines de documentació de sèrie, però l'ecosistema ofereix paquets excel·lents que analitzen les rutes, els controladors, les validacions i les respostes reals de l'API per generar documentació HTML completa amb exemples interactius.

Scribe#

Scribe és l'eina de documentació d'API més popular i millor integrada per a projectes Laravel. A diferència d'altres eines genèriques, Scribe entén les convencions de Laravel: llegeix les regles de validació dels Form Requests per inferir els paràmetres esperats, reconeix les API Resources per generar exemples de resposta, detecta el middleware d'autenticació de Sanctum per marcar quins endpoints requereixen autenticació, i fins i tot pot fer peticions reals a l'API per obtenir respostes d'exemple autèntiques.

La documentació generada inclou una pàgina HTML interactiva amb un panell lateral per navegar entre endpoints, exemples de codi en diversos llenguatges (cURL, JavaScript, PHP, Python), formularis per provar les peticions directament des del navegador, i un motor de cerca per trobar endpoints ràpidament.

Instal·lació#

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

Això publica el fitxer de configuració config/scribe.php, on pots personalitzar el títol, la descripció, els grups de rutes i molt més.

Configuració bàsica#

// config/scribe.php
return [
    'title' => 'API de la meva aplicació',
    'description' => 'Documentació de l\'API REST.',
    'base_url' => env('APP_URL'),
 
    'routes' => [
        [
            'match' => [
                'prefixes' => ['api/*'],
                'domains' => ['*'],
            ],
            'include' => [],
            'exclude' => [],
        ],
    ],
 
    'type' => 'static',  // 'static' genera HTML, 'laravel' utilitza rutes Laravel
 
    'auth' => [
        'enabled' => true,
        'default' => true,
        'in' => 'bearer',
        'name' => 'Authorization',
        'use_value' => 'Bearer {token}',
        'placeholder' => '{token}',
    ],
];

Documentar endpoints#

Scribe extreu informació de les anotacions PHPDoc dels controladors. La documentació de cada endpoint pot incloure una descripció en prosa, els paràmetres que accepta (a la URL, al query string o al cos de la petició), les respostes esperades per a cada escenari (èxit, error de validació, no trobat) i si requereix autenticació. Les anotacions s'escriuen directament als docblocks dels mètodes del controlador, mantenint la documentació al costat del codi que documenta:

/**
 * Gestió d'articles
 *
 * Endpoints per a crear, llistar, actualitzar i eliminar articles.
 */
class ArticleController extends Controller
{
    /**
     * Llistar articles
     *
     * Retorna una llista paginada d'articles publicats. Es pot filtrar
     * per categoria, etiqueta o text de cerca.
     *
     * @queryParam page integer Número de pàgina. Example: 1
     * @queryParam per_page integer Articles per pàgina (màxim 50). Example: 15
     * @queryParam search string Cerca al títol i contingut. Example: Laravel
     * @queryParam category string Slug de la categoria. Example: tutorials
     * @queryParam tag string Slug de l'etiqueta. Example: eloquent
     *
     * @response 200 scenario="Llista d'articles" {
     *   "data": [
     *     {
     *       "id": 1,
     *       "title": "Introducció a Laravel",
     *       "slug": "introduccio-a-laravel",
     *       "excerpt": "Laravel és un framework PHP...",
     *       "is_published": true,
     *       "published_at": "2024-01-15T10:30:00.000000Z"
     *     }
     *   ],
     *   "meta": {
     *     "current_page": 1,
     *     "last_page": 5,
     *     "per_page": 15,
     *     "total": 73
     *   }
     * }
     */
    public function index(Request $request)
    {
        // ...
    }
 
    /**
     * Crear article
     *
     * Crea un article nou associat a l'usuari autenticat.
     *
     * @authenticated
     *
     * @bodyParam title string required El títol de l'article. Example: Nou article
     * @bodyParam body string required El contingut de l'article. Example: El contingut...
     * @bodyParam category_id integer required L'ID de la categoria. Example: 3
     * @bodyParam tags integer[] IDs de les etiquetes. Example: [1, 5, 8]
     * @bodyParam is_published boolean Si es publica immediatament. Example: false
     *
     * @response 201 scenario="Article creat" {
     *   "data": {
     *     "id": 42,
     *     "title": "Nou article",
     *     "slug": "nou-article",
     *     "is_published": false
     *   },
     *   "message": "Article creat correctament."
     * }
     *
     * @response 422 scenario="Errors de validació" {
     *   "message": "The title field is required.",
     *   "errors": {
     *     "title": ["The title field is required."]
     *   }
     * }
     */
    public function store(StoreArticleRequest $request)
    {
        // ...
    }
 
    /**
     * Veure article
     *
     * Retorna els detalls complets d'un article, incloent l'autor,
     * les etiquetes i els comentaris.
     *
     * @urlParam article integer required L'ID de l'article. Example: 1
     *
     * @response 200 scenario="Article trobat" {
     *   "data": {
     *     "id": 1,
     *     "title": "Introducció a Laravel",
     *     "body": "El contingut complet...",
     *     "author": {
     *       "id": 1,
     *       "name": "Joan"
     *     }
     *   }
     * }
     *
     * @response 404 scenario="No trobat" {
     *   "message": "Recurs no trobat."
     * }
     */
    public function show(Article $article)
    {
        // ...
    }
 
    /**
     * Eliminar article
     *
     * Elimina un article. Només l'autor o un administrador pot eliminar-lo.
     *
     * @authenticated
     *
     * @urlParam article integer required L'ID de l'article. Example: 1
     *
     * @response 204 scenario="Eliminat"
     * @response 403 scenario="Sense permís" {
     *   "message": "No tens permís per fer aquesta acció."
     * }
     */
    public function destroy(Article $article)
    {
        // ...
    }
}

Anotacions principals#

Les anotacions de Scribe més utilitzades:

// Classificació
@group Nom del grup         // Agrupar endpoints per secció
 
// Autenticació
@authenticated              // Requereix autenticació
@unauthenticated            // No requereix autenticació (per defecte)
 
// Paràmetres
@urlParam name type description      // Paràmetre a la URL
@queryParam name type description    // Paràmetre de query string
@bodyParam name type description     // Paràmetre al body
@header name value                   // Capçalera necessària
 
// Respostes
@response status scenario json       // Resposta d'exemple
@responseFile file                   // Resposta des d'un fitxer JSON
 
// Altres
@hideFromAPIDocumentation            // Amagar endpoint
@subgroup Nom                        // Subgrup dins d'un grup

Generar la documentació#

php artisan scribe:generate

Amb l'opció type: 'static', Scribe genera els fitxers HTML a public/docs/. Pots accedir a la documentació visitant /docs al navegador.

Amb type: 'laravel', Scribe registra rutes de Laravel per servir la documentació, cosa que permet protegir-la amb middleware:

// config/scribe.php
'type' => 'laravel',
'laravel' => [
    'add_routes' => true,
    'docs_url' => '/docs',
    'middleware' => [],  // Afegir 'auth' per protegir
],

Respostes automàtiques#

Un dels avantatges més grans de Scribe és la capacitat de generar respostes d'exemple automàticament. En lloc d'escriure manualment el JSON de cada resposta (que es pot quedar desactualitzat), Scribe pot fer peticions reals a l'API durant la generació de la documentació, capturant les respostes autèntiques. Això requereix una base de dades de test amb dades de prova (seeders o factories), però garanteix que els exemples reflecteixen exactament el que retorna l'API:

// config/scribe.php
'strategies' => [
    'responses' => [
        Strategies\Responses\UseTransformerTags::class,
        Strategies\Responses\UseApiResourceTags::class,
        Strategies\Responses\UseResponseTag::class,
        Strategies\Responses\UseResponseFileTag::class,
        Strategies\Responses\ResponseCalls::class,  // Peticions reals
    ],
],

Swagger / OpenAPI#

OpenAPI (anteriorment conegut com Swagger) és un estàndard de la indústria per descriure APIs REST de manera formal i independent del llenguatge. Una especificació OpenAPI és un fitxer JSON o YAML que descriu cada endpoint, els seus paràmetres, les respostes, els esquemes de dades i els mecanismes d'autenticació. Eines com Swagger UI renderitzen aquesta especificació com una pàgina web interactiva, i clients com Postman o Insomnia poden importar-la directament per configurar totes les peticions automàticament.

El paquet l5-swagger permet generar documentació OpenAPI per a Laravel a partir d'anotacions als controladors:

composer require darkaonline/l5-swagger
php artisan vendor:publish --provider="L5Swagger\L5SwaggerServiceProvider"

Documentar amb anotacions OpenAPI#

Les anotacions OpenAPI s'escriuen als controladors amb el prefix @OA. Són considerablement més verborses que les de Scribe, però segueixen un estàndard reconegut per tota la indústria. Primer cal definir la informació general de l'API a la classe base del controlador:

use OpenApi\Annotations as OA;
 
/**
 * @OA\Info(
 *     title="API de la meva aplicació",
 *     version="1.0.0",
 *     description="Documentació de l'API REST"
 * )
 */
class Controller extends BaseController
{
}
class ArticleController extends Controller
{
    /**
     * @OA\Get(
     *     path="/api/articles",
     *     summary="Llistar articles",
     *     tags={"Articles"},
     *     @OA\Parameter(
     *         name="page",
     *         in="query",
     *         description="Número de pàgina",
     *         @OA\Schema(type="integer", example=1)
     *     ),
     *     @OA\Parameter(
     *         name="per_page",
     *         in="query",
     *         description="Articles per pàgina",
     *         @OA\Schema(type="integer", example=15)
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="Llista d'articles",
     *         @OA\JsonContent(
     *             @OA\Property(property="data", type="array",
     *                 @OA\Items(ref="#/components/schemas/Article")
     *             )
     *         )
     *     )
     * )
     */
    public function index()
    {
        // ...
    }
 
    /**
     * @OA\Post(
     *     path="/api/articles",
     *     summary="Crear article",
     *     tags={"Articles"},
     *     security={{"bearerAuth": {}}},
     *     @OA\RequestBody(
     *         required=true,
     *         @OA\JsonContent(
     *             required={"title", "body", "category_id"},
     *             @OA\Property(property="title", type="string", example="Nou article"),
     *             @OA\Property(property="body", type="string", example="El contingut..."),
     *             @OA\Property(property="category_id", type="integer", example=3)
     *         )
     *     ),
     *     @OA\Response(response=201, description="Article creat"),
     *     @OA\Response(response=422, description="Errors de validació")
     * )
     */
    public function store(StoreArticleRequest $request)
    {
        // ...
    }
}

Cada endpoint necessita les seves pròpies anotacions amb el path, el mètode HTTP, els paràmetres, el body de la petició i les respostes possibles. Com pots veure, la quantitat d'anotacions creix ràpidament.

Definir esquemes#

Els esquemes defineixen l'estructura dels objectes JSON que retorna l'API. S'utilitzen com a referència compartida entre múltiples endpoints (per exemple, l'esquema Article es pot referenciar tant des de l'endpoint de llistar com des del de detall):

/**
 * @OA\Schema(
 *     schema="Article",
 *     @OA\Property(property="id", type="integer", example=1),
 *     @OA\Property(property="title", type="string", example="Introducció a Laravel"),
 *     @OA\Property(property="slug", type="string", example="introduccio-a-laravel"),
 *     @OA\Property(property="body", type="string"),
 *     @OA\Property(property="is_published", type="boolean", example=true),
 *     @OA\Property(property="published_at", type="string", format="date-time"),
 *     @OA\Property(property="author", ref="#/components/schemas/User")
 * )
 */
class Article extends Model
{
}

Generar la documentació#

php artisan l5-swagger:generate

La documentació Swagger UI serà accessible a /api/documentation.

Scribe vs Swagger#

Scribe s'integra millor amb Laravel perquè entén les convencions del framework: llegeix les validacions dels Form Requests, entén les API Resources, reconeix el middleware d'autenticació de Sanctum i pot inferir respostes dels tipus de retorn. Les anotacions són més concises i menys verborses que les d'OpenAPI.

Swagger/OpenAPI és un estàndard de la indústria que funciona amb qualsevol framework i llenguatge. Si la teva API ha de generar un fitxer OpenAPI compatible (per exemple, per importar-lo a Postman, Insomnia o clients auto-generats), Swagger és la millor opció.

Per a projectes Laravel, Scribe és generalment l'opció més pràctica. Si necessites compatibilitat OpenAPI, Scribe també pot exportar en format OpenAPI a més de la documentació HTML.

Bones pràctiques de documentació#

Una bona documentació no és només una llista tècnica d'endpoints i paràmetres. Ha de ser una guia que permeti a un desenvolupador que no coneix la teva aplicació entendre com funciona l'API, començar a utilitzar-la ràpidament i resoldre problemes quan alguna cosa no funciona com s'esperava.

Descripcions clares#

Cada endpoint hauria de tenir una descripció que expliqui què fa des de la perspectiva del consumidor, no com funciona internament. L'objectiu és que un desenvolupador extern pugui utilitzar l'API sense veure el codi ni preguntar a ningú:

/**
 * Llistar articles
 *
 * Retorna una llista paginada d'articles publicats, ordenats per data
 * de publicació (més recents primer). Els articles no publicats no
 * es mostren excepte per a usuaris autenticats que en siguin els autors.
 */

Exemples reals#

Els exemples de la documentació haurien de ser valors realistes que permetin al desenvolupador entendre el format i el contingut esperat a primer cop d'ull. Un exemple que diu "string" o "1" no transmet cap informació útil, mentre que un valor realista mostra immediatament el format, la llargada típica i el tipus de contingut esperat:

// Bé
@bodyParam title string required El títol de l'article. Example: Com configurar Laravel amb Docker
 
// Malament
@bodyParam title string required El títol. Example: string

Documentar errors#

Els errors són una part tan important de l'API com les respostes d'èxit. Un client necessita saber quins errors pot trobar per gestionar-los correctament a la seva interfície (mostrar un missatge d'error, redirigir al login, etc.). Documentar els errors comuns de cada endpoint estalvia moltes hores de depuració als consumidors de l'API:

/**
 * @response 200 scenario="Èxit" { ... }
 * @response 401 scenario="No autenticat" {"message": "Unauthenticated."}
 * @response 403 scenario="Sense permís" {"message": "No tens permís per fer aquesta acció."}
 * @response 404 scenario="No trobat" {"message": "Recurs no trobat."}
 * @response 422 scenario="Validació" {"message": "The title field is required.", "errors": { ... }}
 * @response 429 scenario="Rate limit" {"message": "Has superat el límit de peticions."}
 */

Agrupar endpoints#

Organitza els endpoints per recurs o funcionalitat perquè la documentació sigui fàcil de navegar:

/**
 * @group Articles
 *
 * Endpoints per gestionar articles del blog.
 */
class ArticleController extends Controller

Mantenir la documentació actualitzada#

La documentació desactualitzada pot ser pitjor que no tenir-ne, perquè els desenvolupadors confien en informació incorrecta i perden temps depurant problemes que no existeixen. La millor manera de mantenir-la al dia és automatitzar la generació. Integra la comanda de generació al teu pipeline de CI/CD perquè la documentació es regeneri automàticament amb cada desplegament:

php artisan scribe:generate --force

O programar-la com a comanda:

// routes/console.php
Schedule::command('scribe:generate --force')->daily();