CAT  ENG  ESP

Creant una API REST amb Laravel

Avui en dia, crear una API REST és una tasca essencial per a qualsevol projecte de desenvolupament web. Laravel, amb la seva potència i flexibilitat, facilita molt aquesta tasca, però com garantir que la teva API sigui segura i robusta? Si estàs començant o vols aprendre com construir una API robusta amb Laravel, aquest article et guiarà pas a pas.

Per seguir endavant et faran falta uns mínims coneixements de programació així com també tenir instal·lades unes quantes coses:

  • Xamp o Mamp per l’entorn de programació
  • Composer per la gestió de llibreries PHP
  • Postman per probar la API

1. Instal·lació de Laravel

El primer pas és tenir una instal·lació de Laravel funcionant. Si encara no tens Laravel instal·lat, pots fer-ho ràpidament utilitzant Composer:

composer create-project --prefer-dist laravel/laravel projecte-api

Això crearà un nou projecte de Laravel. Un cop instal·lat, accedeix a la carpeta del projecte i aixeca el servidor local:

cd projecte-api && php artisan serve

Perfecte ara ja tens Laravel en marxa!

2. Configuració de la base de dades

Laravel facilita molt la connexió amb la base de dades. Només cal que editis el fitxer .env per configurar la connexió:

DB_CONNECTION=mysql 
DB_HOST=127.0.0.1 
DB_PORT=3306 
DB_DATABASE=projecte_api_db
DB_USERNAME=usuari 
DB_PASSWORD=contrasenya

És important remarcar que cal tenir una base de dades operativa, en aquest cas el nom serà projecte_api_db

A partir de Laravel 11 es va treure la api de la configuració inicial per tant si fas servir les últimes versions hauries d’executar:

php artisan install:api

Aquesta comanda crearà tota la configuració inicial i instal·larà el packet de autenticació de Sanctum per tal d’utilitzar la API de Laravel.

3. Creació d’un Model i una Migració

Un cop la base de dades estigui configurada, el següent pas és crear un model que representi les dades de la teva API. Per exemple, si vols gestionar "Articles", pots crear un model amb una migració per gestionar la taula a la base de dades.

php artisan make:model Article -m

Això generarà tant el model com la migració. Obre el fitxer de migració que es troba a database/migrations i defineix els camps que tindrà la taula:

Després, aplica les migracions a la base de dades:

php artisan migrate

Perfecte, ja has creat una taula articles llesta per guardar informació.

4. No exposar dades sensibles en els Models

Una bona pràctica per tal de garantir que les dades sensibles no es passen a l’API es utilitzar hidden de Laravel en els nostres models per tal d’amagar-les.

class User extends Authenticatable
{
    protected $hidden = [
        'password',
        'remember_token',
    ];
}

De la mateixa manera podem escollir quins atributs seran visibles amb la propietat visible

class User extends Authenticatable
{
    protected $visible = [
       'name',
       'email',
    ];
}

En aquest cas només nom i email seran tornats a l’API. Hi ha altres maneres com fer hidden un camp durant una consulta de base de dades però és més eficient definir-ho en els models.

5. Creació dels Endpoints de l’API

El següent pas és definir els endpoints de la teva API. Laravel usa el sistema de rutes per definir els diferents punts d’accés a la teva API. Per exemple, pots començar creant un controlador per gestionar els articles:

php artisan make:controller ArticleController --resource

El controlador amb la bandera --resource genera automàticament les funcions bàsiques CRUD (crear, llegir, actualitzar i esborrar) per a un recurs. A continuació, has de definir les rutes de l’API. Edita el fitxer routes/api.php per afegir les rutes de l’ArticleController:

Route::apiResource('articles', ArticleController::class);

Aquest codi crea automàticament totes les rutes necessàries per a un CRUD complet: llistar articles, crear-ne, actualitzar-ne, obtenir-ne un específic i eliminar-los.

6. Implementació dels mètodes CRUD

Ara, pots implementar la lògica de cada mètode dins del controlador ArticleController. A continuació es mostra un exemple de com podria ser el mètode store, que s'encarrega de crear un nou article:

public function store(Request $request)
{
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'author' => 'required|string|max:50',
        ]);
    
        $article = Article::create([
            'title' => $request->title,
            'content' => $request->content,
            'author' => $request->author,
        ]);
    
        return response()->json($article, Response::HTTP_CREATED);
}

Aquest mètode valida les dades d’entrada, crea un nou article i retorna la resposta en format JSON amb un codi d’estat HTTP 201 (Creat).

7. Configuració de Respostes JSON

Les API REST treballen habitualment amb respostes JSON. Laravel converteix automàticament els models en JSON utilitzant el mètode toJson(). Això facilita molt l'enviament de respostes en aquest format. Només has de retornar el model o la col·lecció de models:

public function index()
{
    return response()->json(Article::all(), Response::HTTP_ACCEPTED);
}

Aquest mètode llista tots els articles de la base de dades i els retorna en format JSON.

Fins aquí hem recreat els primers passos per tenir una API funcional, en els següents veurem com fer-la robusta i quines bones pràctiques hauriem de seguir per fer-la segura i funcional.

8. Autenticació amb Passport o Sanctum

La primera línia de defensa per a la teva API és l'autenticació. Laravel ofereix dues solucions excel·lents per gestionar això: Passport i Sanctum.

  • Laravel Passport és perfecte si necessites OAuth2 i tens requisits més avançats. És robust i permet funcionalitats com la gestió de tokens d'accés.
  • Laravel Sanctum, en canvi, és ideal per a aplicacions SPA o API amb requisits més senzills. Ofereix un mecanisme lleuger però molt efectiu per a l'autenticació amb tokens.

Per començar amb Passport:

composer require laravel/passport
php artisan migrate
php artisan passport:install

Afegeix el trait HasApiTokens al model User, i el sistema ja estarà preparat per gestionar tokens segurs.

Si has creat la API amb la comanda install de Laravel ja tindràs aquest paquet instal·lat i configurat per defecte.

9. Valida sempre les dades d'entrada

Una bona validació d'entrades és crucial per evitar vulnerabilitats com les injeccions SQL o altres atacs. Laravel ofereix un sistema de validació potent i senzill. Aprofita'l!

$request->validate([
    'email' => 'required|email',
    'password' => 'required|min:8'
]);

No només assegura la integritat de les dades, sinó que també millora la robustesa de la teva API davant inputs inesperats.

10. Rate Limiting per prevenir abusos

És fonamental evitar que els usuaris malintencionats facin un ús excessiu de la teva API. Laravel facilita la implementació de Rate Limiting, que limita la quantitat de peticions que un usuari pot fer en un període determinat.

Route::middleware('throttle:60,1')->group(function () {
    Route::get('/article/{id}, 'ArticleController@show');
});

Aquest exemple permet un màxim de 60 peticions per minut. Pots ajustar aquests valors segons les necessitats del teu projecte.

11. CORS: Permet només els dominis que necessites

Controlar qui pot accedir a la teva API és essencial per a la seguretat. Si la teva API serà accedida per diferents dominis, has de configurar correctament les teves polítiques de CORS (Cross-Origin Resource Sharing).

Laravel permet configurar-ho fàcilment al fitxer cors.php, que es troba a config.

'paths' => ['api/*'],
'allowed_origins' => ['https://exemple.com'],

Això assegura que només les peticions des de dominis de confiança puguin accedir a la teva API.

12. Fer servir sempre HTTPS

Encara que sembli obvi, és imprescindible utilitzar HTTPS per a qualsevol API. Això garanteix que la informació viatja xifrada i evita atacs de man-in-the-middle. Configura el servidor per redirigir totes les peticions HTTP a HTTPS i assegura't que Laravel reconeix la connexió segura.

Pots forçar Laravel a utilitzar HTTPS afegint aquest codi al fitxer AppServiceProvider:

if (app()->environment('production')) {
    URL::forceScheme('https');
}

13. Gestió d'errors i excepcions

Una API robusta no només ha de funcionar bé, sinó que ha de saber gestionar els errors correctament. Aprofita la classe Handler de Laravel per personalitzar les respostes d'error.

public function render($request, Exception $exception)
{
    if ($exception instanceof ModelNotFoundException) {
        return response()->json(['error' => 'Resource not found'], 404);
    }
    return parent::render($request, $exception);
}

Això assegura que la teva API retorna missatges d'error clars i coherents.

14. Documentació i testos

Finalment, una bona API ha d'estar ben documentada. Laravel té suport per a eines com Swagger o Postman, que t'ajudaran a documentar els teus endpoints de manera clara i accessible.

A més, assegura't de crear testos per als teus endpoints. Laravel té un sistema de testing molt potent que et permet assegurar-te que cada part de la teva API funciona correctament.

Seguint aquestes bones pràctiques, la teva API REST serà més segura, robusta i fiable. Laravel ofereix totes les eines per fer-ho possible, només cal aprofitar-les correctament. Si tens alguna pregunta o comentari, no dubtis a compartir-lo!

15. Habilitar logs

Laravel ja té integrat el sistema de logs de forma nativa amb Monolog, que et permet registrar diferents tipus de missatges (informatius, advertències, errors, etc.).

Laravel ofereix diferents "canals" per escriure logs. Pots configurar-los al fitxer config/logging.php.

  • Canal per defecte: En aquest fitxer, el canal per defecte està configurat com stack, que és una combinació de canals que pots utilitzar.
  • Per defecte, fa servir el canal single que guarda els logs en un sol fitxer, normalment a storage/logs/laravel.log.

Exemple del fitxer config/logging.php:

'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
],

'single' => [
    'driver' => 'single',
    'path' => storage_path('logs/laravel.log'),
    'level' => 'debug',
],

// Altres opcions com 'daily', 'syslog', 'errorlog', etc.

],

A l’aplicació, pots escriure logs de diverses maneres utilitzant el facade de Log. Per exemple:

use Illuminate\Support\Facades\Log;

Log::info('Missatge informatiu.');
Log::warning('Això és una advertència.');
Log::error('Això és un error.');

Aquests missatges es guardaran al fitxer configurat (storage/logs/laravel.log per defecte).

16. Monitoratge d'una API

El monitoratge de l'API és important per supervisar-ne el rendiment, els errors, les mètriques de tràfic i altres indicadors.

Telescope és una eina desenvolupada per Laravel per monitoritzar i depurar l'aplicació. Proporciona informació sobre les peticions HTTP, excepcions, consultes a la base de dades, tasques programades, etc.

Passos per instal·lar Telescope:

composer require laravel/telescope
php artisan telescope:install
php artisan migrate

Activeu Telescope a l'entorn local modificant el fitxer .env:

TELESCOPE_ENABLED=true

Ara pots accedir a la interfície web de Telescope a /telescope.

Es poden fer servir moltes altres eines de tercers, com per exemple Sentry, New Relic, Datadog…

17. Versionar una API en Laravel

El versionat permet mantenir múltiples versions de l'API i gestionar canvis sense afectar els usuaris actuals, mentre que la deprecació d'endpoints t'ajuda a avisar els usuaris que certs endpoints quedaran obsolets.

Laravel proporciona una forma flexible per versionar la teva API, normalment a través de les rutes. El versionat es pot fer via el prefixat de les rutes o utilitzant espais de noms (namespaces) a través dels controladors.

Una manera comuna de versionar una API és afegir un prefix de versió a les rutes dins del fitxer routes/api.php.

Route::prefix('v1')->group(function () {
    Route::get('articles', [ArticleController::class, 'index']);
    Route::get('articles/{id}', [ArticleController::class, 'show']);
});

Route::prefix('v2')->group(function () {
    Route::get('articles', [NouArticleController::class, 'index']);
    Route::get('articles/{id}', [NouArticleController::class, 'show']);
});

En aquest exemple, hi ha dues versions de l'API (v1 i v2), i cadascuna utilitza un controlador diferent. Això et permet fer canvis a la nova versió de l'API (v2) sense trencar la compatibilitat amb la versió antiga.

18. Aplicar arquitectures a la API

Per tal de fer la API més robusta podem triar diferents arquitectures. Una d’aquestes arquitectures seria la DDD (Domain-Driven Design), aquesta arquitectura permet separar de manera clara la part d’entrada i sortida de dades de la API, la part de on tindrem la nostre llògica de negoci i finalment la part que fa consultes a la base de dades.

Una de les millors parts d’aquesta arquitectura es que el desenvolupador pot canviar parts del  codi sense que la resta es vegi afectat. Com per exemple canviar de una base de dades relacional amb Mysql a una de no relacional com seria MongoDB. En aquest cas només hauriem de canviar la capa de infraestructura i tot funcionaria igual.

Organitza el projecte en mòduls o àrees del domini

En lloc de col·locar-ho tot en carpetes predeterminades de Laravel com Models, Controllers, etc., és millor estructurar les carpetes segons el domini. Això proporciona un conjunt de mòduls més cohesionats i separa clarament la lògica de negoci.

Un possible esquema de carpetes podria ser:

app/
├── Domini/
│ ├── Usuari/
│ │ ├── Entitats/
│ │ ├── Valors/
│ │ ├── Repositoris/
│ │ └── Serveis/
│ └── Ordre/
├── Aplicacio/
│ ├── Comandes/
│ ├── Usuaris/
│ └── Ordres/
├── Infraestructura/
│ ├── Persistencia/
│ ├── Repositoris/
│ └── ServeisExterns/
└── Interficie/
├── HTTP/
└── API/

  • Domini: Inclou tot el que representa el nucli del teu negoci. Per exemple, Usuari tindria les entitats (Usuari, Adreça), objectes de valor i les interfícies dels repositoris.
  • Aplicació: Gestiona les accions i els fluxos del sistema. Aquí hi col·loques les comandes o casos d’ús que orchestren les operacions del domini.
  • Infraestructura: Conté la implementació dels repositoris, serveis externs, bases de dades, etc.
  • Interfície: On es gestionen les rutes API i els controladors que interactuen amb la capa d’aplicació.

Domini: Entitats, objectes de valor i regles de negoci

Les entitats són els objectes centrals de la lògica de negoci que tenen identitat. Els objectes de valor són tipus que defineixen atributs però no tenen identitat (per exemple, una adreça o un correu electrònic).

Exemple d’entitat Usuari:

namespace App\Domini\Usuari\Entitats;

class Usuari
{
private $id;
private $nom;
private $email;

public function __construct($id, $nom, $email)
{
    $this->id = $id;
    $this->nom = $nom;
    $this->email = $email;
}

public function actualitzarNom($nouNom)
{
    // Lògica de negoci per validar i canviar el nom
    $this->nom = $nouNom;
}

public function obtenirNom()
{
    return $this->nom;
}

Els repositoris defineixen com recuperar i guardar aquestes entitats:

namespace App\Domini\Usuari\Repositoris;

interface UsuariRepositori
{
    public function obtenirPerId($id): ?Usuari;
    public function guardar(Usuari $usuari): void;
}

 Aplicació: Casos d'ús i serveis

Els casos d'ús gestionen la coordinació entre les entitats i els repositoris del domini. Aquests casos representen accions o comandes que s'han de dur a terme, però no contenen lògica de negoci.

Exemple d’un cas d’ús per actualitzar el nom d’un usuari:

namespace App\Aplicacio\Usuaris;

use App\Domini\Usuari\Repositoris\UsuariRepositori;

class ActualitzarNomUsuari
{
    private $usuariRepositori;

    public function __construct(UsuariRepositori $usuariRepositori)
    {
        $this->usuariRepositori = $usuariRepositori;
    }

    public function executar($id, $nouNom)
    {
        $usuari = $this->usuariRepositori->obtenirPerId($id);
        $usuari->actualitzarNom($nouNom);
        $this->usuariRepositori->guardar($usuari);
    }
}

Infraestructura: Persistència i serveis externs

En aquesta capa implementem els repositoris i la persistència de dades. Utilitza Eloquent o altres ORM per persistir les entitats, però mantingues la interfície separada del domini.

Exemple d'implementació del repositori d'usuaris:

namespace App\Infraestructura\Persistencia;
use App\Domini\Usuari\Repositoris\UsuariRepositori;
use App\Domini\Usuari\Entitats\Usuari;

class EloquentUsuariRepositori implements UsuariRepositori
{
    public function obtenirPerId($id): ?Usuari
    {
        $usuariModel = UsuariModel::find($id);
        if ($usuariModel === null) {
            return null;
        }

        return new Usuari($usuariModel->id, $usuariModel->nom, $usuariModel->email);
    }

    public function guardar(Usuari $usuari): void
    {
        $usuariModel = UsuariModel::find($usuari->obtenirId()) ?? new UsuariModel();
        $usuariModel->nom = $usuari->obtenirNom();
        $usuariModel->email = $usuari->obtenirEmail();
        $usuariModel->save();
    }

Interfície: API i controladors

Els controladors a l'API haurien d’estar nets i delegar la lògica de negoci als serveis d'aplicació.

namespace App\Interficie\API;
use App\Aplicacio\Usuaris\ActualitzarNomUsuari;
use Illuminate\Http\Request;

class UsuariController
{
    private $actualitzarNomUsuari;

    public function __construct(ActualitzarNomUsuari $actualitzarNomUsuari)
    {
        $this->actualitzarNomUsuari = $actualitzarNomUsuari;
    }

    public function actualitzarNom(Request $request, $id)
    {
        $nouNom = $request->input('nom');
        $this->actualitzarNomUsuari->executar($id, $nouNom);

        return response()->json(['message' => 'Nom actualitzat correctament.']);
    }
}
cross