Hasina Razafintsalama

Hasina RAZAFINTSALAMA

← Retour au Blog
Backend

Concevoir une API REST robuste et scalable avec Laravel

Laravel construit une API REST vite, mais vite ne veut pas dire prête pour la production. Voici les décisions structurelles qui décident si elle scale : structure, resources, validation, versioning, auth, rate limiting, pagination, erreurs, doc et tests.

2026-05-28·12 min

Laravel est l'un des meilleurs frameworks pour construire une API REST rapidement, mais rapide ne signifie pas prêt pour la production par défaut. Une poignée de décisions structurelles prises au départ décident si l'API scale gracieusement ou devient un fardeau de maintenance. Voici la checklist à parcourir avant de livrer la première version.

Structure du projet

Placez les contrôleurs sous Api/V1 et gardez-les fins. Le contrôleur accepte une requête validée, appelle une action ou un service, et renvoie une resource. La validation vit dans les Form Requests, la mise en forme de la sortie dans les API Resources, la logique métier dans des classes Action ou Service. Une méthode de contrôleur qui dépasse quelques lignes signale en général de la logique qui devrait vivre ailleurs.

php
// app/Http/Controllers/Api/V1/OrderController.php
public function store(StoreOrderRequest $request, CreateOrder $createOrder)
{
    $order = $createOrder->handle($request->toDto());

    return (new OrderResource($order))
        ->response()
        ->setStatusCode(201);
}

API Resources, pas les modèles bruts

Retourner des modèles Eloquent directement couple les clients à votre schéma de base et fait fuir des champs internes. Une API Resource est le contrat explicite et stable entre vos données et vos consommateurs. Utilisez whenLoaded pour que les relations n'apparaissent que si elles ont été eager-loadées, ce qui vous garde aussi honnête sur les N+1.

php
class OrderResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'total' => $this->total,
            'status' => $this->status,
            'created_at' => $this->created_at->toISOString(),
            'customer' => new CustomerResource($this->whenLoaded('customer')),
        ];
    }
}

Validation avec les Form Requests

Mettez chaque règle dans un Form Request, pas dans le contrôleur. Ça garde la validation à un seul endroit, vous donne authorize() pour les vérifications de politique sur le même objet, et renvoie un 422 cohérent automatiquement. Convertissez les données validées en DTO pour que le reste du code travaille avec des valeurs typées.

php
class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Order::class);
    }

    public function rules(): array
    {
        return [
            'customer_id' => ['required', 'integer', 'exists:customers,id'],
            'lines' => ['required', 'array', 'min:1'],
            'lines.*.sku' => ['required', 'string'],
            'lines.*.quantity' => ['required', 'integer', 'min:1'],
        ];
    }
}

Versioning dès le premier jour

Utilisez le versioning par URI : un groupe de routes par version sous /api/v1, chacun avec ses propres contrôleurs et resources. Ça ne coûte rien à ajouter au départ et ça permet de faire tourner l'ancienne et la nouvelle forme côte à côte. Gardez la version précédente en service jusqu'à ce que les clients aient migré, et annoncez une date de retrait bien à l'avance.

php
// routes/api.php
Route::prefix('v1')
    ->name('api.v1.')
    ->group(base_path('routes/api/v1.php'));

Authentification : Sanctum, Passport ou JWT

Sanctum pour votre propre client SPA ou mobile, ce qui couvre la plupart des cas avec un minimum de complexité. Passport quand vous avez besoin d'un serveur OAuth2 complet pour des clients tiers. JWT uniquement quand plusieurs services indépendants doivent vérifier le même token sans store de session partagé, un cas plus étroit avec ses propres compromis.

Rate limiting

Définissez des limiteurs nommés indexés sur l'utilisateur ou la clé d'API, appliquez un défaut à toute l'API, et fixez des limites plus strictes sur les routes coûteuses ou sensibles. Retournez un 429 avec un header Retry-After pour que les clients réduisent leur cadence correctement.

php
RateLimiter::for('api', fn (Request $request) =>
    $request->user()
        ? Limit::perMinute(120)->by($request->user()->id)
        : Limit::perMinute(20)->by($request->ip())
);

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::apiResource('orders', OrderController::class);
});

Pagination, filtrage et tri

Paginez chaque endpoint de liste avec une taille de page par défaut et un maximum dur, et préférez cursorPaginate pour les jeux de données grands ou qui changent vite. Pour le filtrage et le tri, mettez en liste blanche les champs autorisés explicitement plutôt que de passer l'input de la requête au query builder.

php
$orders = QueryBuilder::for(Order::class)
    ->allowedFilters(['status', 'customer_id'])
    ->allowedSorts(['created_at', 'total'])
    ->allowedIncludes(['customer'])
    ->cursorPaginate(50);

Réponses d'erreur cohérentes

Mappez chaque exception vers un seul format dans le handler global : les problem details RFC 9457, servis en application/problem+json. Les erreurs de validation, de ressource introuvable, d'autorisation et de rate limit sortent toutes avec la même forme, donc les clients écrivent un seul chemin d'erreur.

Documentation et tests

Générez une description OpenAPI depuis le code (un package comme Scramble lit vos Form Requests et Resources) pour que la doc ne puisse pas dériver de l'implémentation. Couvrez chaque endpoint avec un feature test qui vérifie le statut, la structure JSON et les règles d'autorisation.

php
public function test_il_liste_les_commandes_de_l_utilisateur_authentifie(): void
{
    $user = User::factory()->has(Order::factory()->count(3))->create();

    $this->actingAs($user)
        ->getJson('/api/v1/orders')
        ->assertOk()
        ->assertJsonCount(3, 'data')
        ->assertJsonStructure(['data' => [['id', 'total', 'status']]]);
}

Les briques, réunies

PréoccupationBrique
StructureContrôleurs fins sous Api/V1, logique dans des actions
SortieAPI Resources comme contrat, whenLoaded pour les relations
EntréeForm Requests avec rules() et authorize()
VersioningPréfixe URI, un groupe de routes par version
AuthSanctum par défaut, Passport pour OAuth2, JWT pour le multi-service
AbusLimiteurs nommés, 429 avec Retry-After
ListesToujours paginer, liste blanche des filtres et des tris
ErreursProblem details RFC 9457 depuis le handler global
Doc et testsOpenAPI généré depuis le code, un feature test par endpoint

Chaque élément de cette liste est peu coûteux à ajouter dans la première version et cher à rattraper une fois que l'API a des clients. Passez une heure sur la checklist avant de livrer.

FAQ

Comment structurer une API REST Laravel ?
Placez les contrôleurs sous Api/V1, gardez-les fins, et poussez le travail vers l'extérieur : Form Requests pour la validation, API Resources pour la sortie, et classes Action ou Service pour la logique métier. Le contrôleur accepte une requête validée, appelle une chose, et renvoie une resource.
Sanctum, Passport ou JWT pour une API Laravel ?
Sanctum pour votre propre client SPA ou mobile, ce qui est la plupart des cas. Passport quand vous avez besoin d'un serveur OAuth2 complet pour des clients tiers. JWT uniquement quand plusieurs services indépendants doivent vérifier le même token sans store de session partagé.
Comment versionner une API Laravel ?
Le versioning par URI est le plus simple : un groupe de routes par version sous /api/v1, /api/v2, chacun avec ses propres contrôleurs et resources. Gardez la version précédente en service jusqu'à ce que les clients migrent, et annoncez une date de retrait bien à l'avance.
Faut-il utiliser les API Resources ?
Oui. Retourner des modèles Eloquent directement couple les clients à votre schéma de base et fait fuir des champs internes. Une API Resource est le contrat explicite et stable entre vos données et vos consommateurs, et c'est là que vous contrôlez les includes, le formatage et la visibilité.
Comment paginer une grosse liste dans une API Laravel ?
Utilisez paginate ou cursorPaginate avec une taille de page par défaut et un maximum dur. La pagination par curseur est le bon choix pour les jeux de données grands ou qui changent vite, car elle ne saute ni ne duplique de lignes quand les données changent entre deux requêtes de page.

Une API Laravel prête pour la production n'est pas plus de code qu'une API rapide, c'est le même code avec la structure décidée exprès. Parcourez cette checklist pour la première version et l'API reste facile à faire évoluer à mesure que le produit et le trafic grandissent.

Besoin d'aide sur ce sujet ? Conception d'API REST

Découvrir ce service