API REST avec Laravel et API Resources

Première leçon du cours Laravel avancé (API, files d'attente, tests), en accès libre : environ 20 minutes, 4 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Exposer une ressource en API REST avec apiResource
  • Contrôler le format JSON avec des API Resources
  • Renvoyer les bons codes HTTP et des collections paginées

Routes d'APIAPI ResourcesCodes HTTP

Une application mobile, un front-end React ou un partenaire consomment vos données en JSON via une API. Laravel rend cela très simple, à condition de maîtriser ce que l’on expose.

Activer les routes d’API

Depuis Laravel 11, le squelette ne contient plus routes/api.php. On l’ajoute avec :

php artisan install:api

Cette commande crée routes/api.php (toutes ses routes sont préfixées par /api) et installe Laravel Sanctum, utilisé à la leçon suivante.

// routes/api.php
use App\Http\Controllers\Api\ProductController;

Route::apiResource('products', ProductController::class);
// GET /api/products, POST /api/products, GET|PUT|PATCH|DELETE /api/products/{product}

Les API Resources

Renvoyer Product::all() exposerait toutes les colonnes, y compris des champs internes, et lierait votre format JSON au schéma de la base. Une API Resource décrit explicitement la représentation publique :

php artisan make:resource ProductResource
class ProductResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'        => $this->id,
            'nom'       => $this->nom,
            'prix'      => $this->prix_centimes / 100,         // format adapté au client
            'en_stock'  => $this->stock > 0,
            'categorie' => new CategoryResource($this->whenLoaded('category')),
            'cree_le'   => $this->created_at->toIso8601String(),
        ];
    }
}

whenLoaded('category') n’inclut la catégorie que si elle a été chargée avec with('category') : la ressource ne déclenche jamais de requête supplémentaire en douce.

Le contrôleur d’API

class ProductController extends Controller
{
    public function index()
    {
        return ProductResource::collection(
            Product::with('category')->latest()->paginate(20)
        );   // { "data": [...], "links": {...}, "meta": {...} }
    }

    public function store(StoreProductRequest $request)
    {
        $product = Product::create($request->validated());
        return (new ProductResource($product))->response()->setStatusCode(201);
    }

    public function show(Product $product)
    {
        return new ProductResource($product->load('category'));
    }

    public function destroy(Product $product)
    {
        $product->delete();
        return response()->noContent();   // 204
    }
}

Une collection paginée produit automatiquement trois clés : data (les éléments), links (first, last, prev, next) et meta (current_page, last_page, per_page, total…).

Codes de statut et erreurs

Situation Code
Lecture réussie 200
Création 201
Suppression sans contenu 204
Non authentifié / interdit 401 / 403
Ressource absente 404
Validation échouée 422

Dans un projet Laravel 13, bootstrap/app.php demande déjà de rendre en JSON les exceptions des routes api/* ($exceptions->shouldRenderJsonWhen(...)) : une erreur de validation y produit une réponse 422 en JSON. Le client doit tout de même envoyer l’en-tête Accept: application/json : sans lui, le middleware d’authentification tente de rediriger un client non connecté vers une route login au lieu de répondre 401. Les erreurs de validation prennent la forme {"message": "...", "errors": {"nom": ["..."]}}.

Testez rapidement avec curl :

curl -s -H "Accept: application/json" http://127.0.0.1:8000/api/products?page=2

En résumé : routes d’API dédiées, ressources explicites, relations chargées volontairement et codes HTTP justes. C’est ce qui rend une API prévisible pour ceux qui la consomment.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 2Sandbox serveur2 indices

    Écrire une ressource en PHP pur

    Écrivez produitResource(array $p): array, l’équivalent d’une ProductResource. À partir d’une ligne ['id', 'nom', 'prix_centimes', 'stock', 'cout_achat', ...], renvoyez :

    • id, nom ;
    • prix en euros (float arrondi à 2 décimales) ;
    • en_stock (booléen) ;
    • categorie (['id', 'nom']) uniquement si la clé categorie est présente (simulation de whenLoaded).

    Les autres champs (comme cout_achat) ne doivent jamais apparaître.

    function produitResource(array $p): array
    {
    }
  2. CodeDifficulté 3Sandbox serveur2 indices

    Réponse paginée au format Laravel

    Écrivez reponsePaginee(array $items, int $page, int $parPage, string $url): array qui renvoie ['data' => ..., 'links' => ['first', 'last', 'prev', 'next'], 'meta' => ['current_page', 'last_page', 'per_page', 'total']].

    • Les liens ont la forme $url . '?page=N' ; prev/next valent null s’ils n’existent pas.
    • last_page vaut au moins 1 ; la page demandée est ramenée entre 1 et last_page.
    function reponsePaginee(array $items, int $page, int $parPage, string $url): array
    {
    }
  3. TâcheDifficulté 22 indices

    Une API produits avec apiResource

    Dans votre projet Laravel : exécutez php artisan install:api, créez le modèle Product (nom, prix_centimes, stock), une ProductResource et un contrôleur Api\ProductController avec index, store, show et destroy. Déclarez Route::apiResource('products', ...) et testez chaque route avec curl en envoyant Accept: application/json.

    • GET /api/products renvoie data, links et meta
    • POST /api/products valide renvoie 201 et le produit au format de la ressource
    • POST invalide renvoie 422 avec la clé errors
    • DELETE /api/products/{id} renvoie 204 et GET sur le même id renvoie ensuite 404
  4. QCMDifficulté 11 indice

    Le bon code HTTP

    Quel code renvoyer après la suppression réussie d’une ressource, sans corps de réponse ?

    • 200
    • 204
    • 404

Quiz de fin de leçon

  1. Que crée php artisan install:api ?

    • Le fichier routes/api.php et l'installation de Sanctum
    • Un nouveau projet Laravel
    • Une documentation OpenAPI
  2. Quel est l’intérêt principal d’une API Resource ?

    • Accélérer la base de données
    • Définir explicitement les champs JSON exposés, indépendamment du schéma
    • Remplacer la validation
  3. Que fait $this->whenLoaded(‘category’) dans une ressource ?

    • Charge la catégorie si besoin
    • N'inclut la catégorie que si la relation a déjà été chargée
    • Lève une erreur si la catégorie est absente

La suite du cours

  1. Authentification d'API avec Sanctum et autorisations20 min
  2. Files d'attente et jobs20 min
  3. Performances : requêtes N+1, eager loading et cache20 min
  4. Tester une application Laravel avec Pest et PHPUnit20 min

Laravel avancé (API, files d'attente, tests) · Avancé · 4 h 10