API REST in Laravel: fondamenti e primo endpoint
Questo articolo apre una serie di sette puntate dedicate alla costruzione di API REST con Laravel. Partiremo dalle fondamenta, con la creazione di un progetto e il primo endpoint JSON, e arriveremo progressivamente a un'applicazione completa dotata di persistenza, trasformazione dei dati, validazione, autenticazione e test automatici. Il filo conduttore sarà sempre lo stesso: un catalogo di libri, che faremo crescere articolo dopo articolo. In questa prima puntata ci concentriamo sui concetti architetturali di REST, sul motivo per cui Laravel è uno strumento eccellente per esporre API e sulla creazione del primo endpoint funzionante.
La serie presuppone Laravel 13, la versione stabile corrente, che richiede PHP 8.3 o superiore. Tutto ciò che vedremo si applica senza modifiche anche a Laravel 11 e 12, perché la superficie delle API su cui lavoreremo è rimasta stabile fra queste versioni.
Che cosa significa REST
REST, acronimo di Representational State Transfer, non è un protocollo né una libreria, ma uno stile architetturale definito da Roy Fielding nel 2000. Un'API che aderisce a questo stile espone delle risorse identificate da URL e le manipola attraverso i metodi del protocollo HTTP. L'idea centrale è che il server non conserva lo stato della conversazione con il client: ogni richiesta contiene tutte le informazioni necessarie per essere elaborata. Questa proprietà, detta stateless, rende le API REST facili da scalare orizzontalmente, perché qualsiasi istanza del server è in grado di rispondere a qualsiasi richiesta.
Una risorsa è un'entità concettuale del dominio applicativo: nel nostro caso, un libro. Ogni risorsa è raggiungibile tramite un identificatore univoco, l'URL, e le operazioni su di essa vengono espresse dai metodi HTTP. La corrispondenza tipica tra i metodi e le operazioni CRUD (Create, Read, Update, Delete) è la seguente:
- Il metodo GET recupera una rappresentazione della risorsa senza modificarla.
- Il metodo POST crea una nuova risorsa all'interno di una collezione.
- Il metodo PUT sostituisce integralmente una risorsa esistente.
- Il metodo PATCH applica una modifica parziale a una risorsa.
- Il metodo DELETE rimuove la risorsa.
A ogni richiesta il server risponde con un codice di stato numerico che ne comunica l'esito. I codici nella famiglia 2xx indicano successo, quelli 4xx segnalano un errore imputabile al client (per esempio una risorsa inesistente o dati malformati), mentre quelli 5xx indicano un problema interno del server. Rispettare la semantica di metodi e codici di stato è ciò che distingue un'API realmente RESTful da un semplice insieme di endpoint HTTP.
Perché Laravel per le API REST
Laravel nasce come framework full-stack, ma nel corso degli anni si è affermato anche come una delle piattaforme più produttive per costruire API. La ragione è che moltissimo del lavoro ripetitivo è già risolto dal framework: il routing dichiarativo, l'iniezione delle dipendenze, la serializzazione JSON automatica, la validazione delle richieste, l'ORM Eloquent per la persistenza e un sistema di autenticazione a token pronto all'uso. Aggiungere un endpoint significativo richiede spesso poche righe, e il codice risultante è leggibile e uniforme.
A partire da Laravel 11 lo scheletro del framework è stato snellito. Non esistono più i file Kernel.php per HTTP e console, e la configurazione di middleware, gestione delle eccezioni e routing è centralizzata in un unico file, bootstrap/app.php. Anche il file delle rotte API non è più presente di default: va abilitato con un comando dedicato, come vedremo tra poco. Questa scelta rende i progetti più essenziali e ci costringe ad attivare consapevolmente ciò che ci serve.
Creare il progetto
Assumiamo di avere già a disposizione PHP 8.3 o superiore e Composer. Creiamo un nuovo progetto con il seguente comando.
composer create-project laravel/laravel catalogo-libri
cd catalogo-libri
Il comando scarica lo scheletro dell'applicazione e installa tutte le dipendenze. Al termine possiamo già avviare il server di sviluppo integrato.
php artisan serve
Il server si mette in ascolto su http://localhost:8000. Aprendo quell'indirizzo nel browser vedremo la pagina di benvenuto di Laravel. Questa è però l'interfaccia web: a noi interessano le API.
Abilitare le rotte API
Come anticipato, il file routes/api.php non è presente in un'installazione recente. Per crearlo e per predisporre l'infrastruttura di autenticazione a token che useremo più avanti, eseguiamo il comando dedicato.
php artisan install:api
Il comando genera il file routes/api.php, registra il relativo gruppo di rotte con il prefisso /api all'interno di bootstrap/app.php e installa Laravel Sanctum, il pacchetto che gestirà l'autenticazione. Durante l'esecuzione verrà chiesto se si desidera eseguire le migrazioni: possiamo accettare, oppure rimandare, perché ce ne occuperemo nella puntata dedicata alla persistenza.
Aprendo bootstrap/app.php troveremo la configurazione del routing, che ora include esplicitamente il file delle API.
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
api: __DIR__ . '/../routes/api.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// La configurazione dei middleware vivrà qui
})
->withExceptions(function (Exceptions $exceptions) {
// La gestione delle eccezioni vivrà qui
})->create();
La chiave importante è api, che punta al nostro file di rotte. Tutto ciò che dichiareremo lì dentro sarà automaticamente servito sotto il prefisso /api e sarà privo di stato di sessione, esattamente come richiede un'API REST.
Il primo endpoint
Cominciamo con l'endpoint più semplice e utile che esista: un health check, ovvero una rotta che conferma che il servizio è attivo. Apriamo routes/api.php e aggiungiamo una rotta con una funzione anonima come gestore.
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/health', function (Request $request) {
// response()->json() imposta l'header Content-Type e serializza l'array in JSON
return response()->json([
'status' => 'ok',
'service' => 'catalogo-libri',
]);
});
Interroghiamo l'endpoint dal terminale con curl. Nota il prefisso /api nel percorso.
curl -i http://localhost:8000/api/health
La risposta conterrà l'intestazione Content-Type: application/json, il codice di stato 200 e il corpo {"status":"ok","service":"catalogo-libri"}. L'helper response()->json() si è occupato di tutto: ha serializzato l'array associativo in JSON, ha impostato l'intestazione corretta e ha usato il codice di stato 200 di default. Possiamo passare un secondo argomento per specificare un codice diverso, come vedremo quando creeremo risorse.
Dai gestori anonimi ai controller
Definire la logica direttamente nelle funzioni anonime dentro il file delle rotte è comodo per un esempio, ma non scala. In un'applicazione reale la logica di ogni gruppo di endpoint vive in un controller, una classe dedicata. Generiamo il nostro primo controller con Artisan.
php artisan make:controller HealthController
Il comando crea il file app/Http/Controllers/HealthController.php. Vi aggiungiamo un metodo che restituisce lo stato del servizio, arricchendolo con qualche informazione utile.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
class HealthController extends Controller
{
// Restituisce lo stato di salute del servizio insieme al timestamp corrente
public function show(): JsonResponse
{
return response()->json([
'status' => 'ok',
'service' => 'catalogo-libri',
'timestamp' => now()->toIso8601String(),
]);
}
}
Ora colleghiamo la rotta al metodo del controller. Modifichiamo routes/api.php sostituendo la funzione anonima con un riferimento alla classe.
<?php
use App\Http\Controllers\HealthController;
use Illuminate\Support\Facades\Route;
Route::get('/health', [HealthController::class, 'show']);
Il comportamento visibile è identico, ma la struttura è molto migliore: le rotte descrivono soltanto la corrispondenza tra URL e azione, mentre la logica risiede nei controller. Questa separazione diventa essenziale man mano che l'applicazione cresce.
Il tipo di ritorno JsonResponse
Hai notato l'annotazione di tipo : JsonResponse sul metodo. Laravel non la richiede, ma dichiarare esplicitamente il tipo restituito rende il codice più chiaro e permette agli strumenti di analisi statica di verificarne la correttezza. In realtà, se un metodo di un controller restituisce direttamente un array, il framework lo converte automaticamente in una risposta JSON. Il metodo seguente è quindi equivalente al precedente dal punto di vista del client.
// Laravel converte automaticamente l'array restituito in una risposta JSON
public function show(): array
{
return [
'status' => 'ok',
'service' => 'catalogo-libri',
];
}
Nel corso della serie useremo prevalentemente response()->json() quando dobbiamo controllare il codice di stato, e restituiremo oggetti dedicati (le API Resource) quando dovremo trasformare i dati del dominio. Restituire un array grezzo, invece, lo eviteremo per le risorse vere e proprie, perché espone la struttura interna dei nostri dati senza alcun controllo.
Ispezionare le rotte registrate
Artisan offre un comando prezioso per verificare quali rotte sono attive, con quale metodo e quale gestore. Eseguiamolo filtrando per il prefisso delle API.
php artisan route:list --path=api
L'output elenca la nostra rotta GET api/health associata al metodo HealthController@show. Durante lo sviluppo di un'API questo comando è il modo più rapido per avere una fotografia dell'interfaccia esposta, senza dover leggere manualmente il file delle rotte.
Conclusione
In questa prima puntata abbiamo posto le fondamenta. Abbiamo chiarito che cosa significhi REST, perché lo stile stateless favorisca la scalabilità e come Laravel, con il suo scheletro snello introdotto dalla versione 11, ci permetta di esporre API con pochissima cerimonia. Abbiamo creato il progetto, abilitato le rotte API con install:api, scritto un primo endpoint di health check e spostato la logica in un controller dedicato.
Nella prossima puntata entreremo nel vivo del nostro dominio: definiremo le rotte per la risorsa libro sfruttando i controller di risorsa e il metodo Route::apiResource, che con una sola riga genera l'intero insieme di endpoint CRUD. Getteremo così l'impalcatura su cui, nelle puntate successive, innesteremo la persistenza con Eloquent.