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.
Écrire une ressource en PHP pur
Écrivez
produitResource(array $p): array, l’équivalent d’uneProductResource. À partir d’une ligne['id', 'nom', 'prix_centimes', 'stock', 'cout_achat', ...], renvoyez :id,nom;prixen euros (float arrondi à 2 décimales) ;en_stock(booléen) ;categorie(['id', 'nom']) uniquement si la clécategorieest présente (simulation dewhenLoaded).
Les autres champs (comme
cout_achat) ne doivent jamais apparaître.function produitResource(array $p): array { }Réponse paginée au format Laravel
Écrivez
reponsePaginee(array $items, int $page, int $parPage, string $url): arrayqui renvoie['data' => ..., 'links' => ['first', 'last', 'prev', 'next'], 'meta' => ['current_page', 'last_page', 'per_page', 'total']].- Les liens ont la forme
$url . '?page=N';prev/nextvalentnulls’ils n’existent pas. last_pagevaut au moins 1 ; la page demandée est ramenée entre 1 etlast_page.
function reponsePaginee(array $items, int $page, int $parPage, string $url): array { }- Les liens ont la forme
Une API produits avec apiResource
Dans votre projet Laravel : exécutez
php artisan install:api, créez le modèleProduct(nom, prix_centimes, stock), uneProductResourceet un contrôleurApi\ProductControlleravecindex,store,showetdestroy. DéclarezRoute::apiResource('products', ...)et testez chaque route avec curl en envoyantAccept: 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
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
Que crée php artisan install:api ?
- Le fichier routes/api.php et l'installation de Sanctum
- Un nouveau projet Laravel
- Une documentation OpenAPI
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
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
- Authentification d'API avec Sanctum et autorisations20 min
- Files d'attente et jobs20 min
- Performances : requêtes N+1, eager loading et cache20 min
- Tester une application Laravel avec Pest et PHPUnit20 min