CyberSDF

Reprenez la main sur votre numérique

Créer une API REST Laravel avec Sanctum : CRUD complet

Ordinateur portable affichant une route API et une réponse JSON.

Verdict : pour une API Laravel 13 neuve, utilisez install:api, une Form Request, une API Resource et Route::apiResource. Sanctum authentifie le token, mais vos policies et abilities doivent encore décider quelles ressources il peut modifier.

Sommaire
  1. Le montage commence par les routes API
  2. Préparer la table et le modèle Program
  3. Valider une requête avant de la persister
  4. Fixer la forme de la réponse avec une Resource
  5. Écrire le contrôleur sans mélanger les rôles
  6. Protéger les routes avec un token Sanctum
  7. Vérifier les cinq opérations avec curl
  8. Lire les erreurs au lieu de les masquer
  9. Ce que l’ancien tutoriel Laravel 8 change aujourd’hui
  10. Poursuivre avec le terminal et HTTP

Le résultat attendu est une ressource Program accessible en JSON : création, liste, lecture, modification et suppression. Les exemples utilisent une base programs, un token Bearer et des commandes curl afin que le verbe, l’URL, les en-têtes et le statut restent visibles.

La documentation officielle Sanctum actuelle décrit Laravel 13.x et la commande install:api. La procédure ci-dessous suit cette organisation, au lieu de reprendre l’ajout manuel de middleware dans app/Http/Kernel.php proposé par l’ancien tutoriel Laravel 8.

Le montage commence par les routes API

Dans un projet neuf, installez les dépendances puis activez les routes API :

composer create-project laravel/laravel api-programmes
cd api-programmes
php artisan install:api

install:api installe Sanctum et prépare routes/api.php. Ces routes sont stateless et reçoivent automatiquement le préfixe /api. Votre ressource sera donc appelée par /api/programs, même si le fichier ne répète pas ce préfixe.

Le préfixe ajouté par le groupe API évite de disperser la même information dans chaque route et rend la liste finale facile à relire.

La documentation Laravel sur le routage décrit ce chargement et la documentation Sanctum détaille les tokens, les sessions SPA et la protection des routes.

Préparer la table et le modèle Program

Générez le modèle et sa migration :

php artisan make:model Program -m
php artisan migrate

Dans la migration créée sous database/migrations, définissez les colonnes dont l’API a besoin :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('programs', function (Blueprint $table): void {
            $table->id();
            $table->string('name', 120);
            $table->text('description');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('programs');
    }
};

Le modèle doit accepter uniquement les deux attributs envoyés par le client. Une liste blanche est plus lisible qu’un $guarded vide :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Program extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'description',
    ];
}

Relancez php artisan migrate après toute modification de la migration sur une base de développement. Si la table existe déjà et que le schéma change, créez une nouvelle migration au lieu de réécrire une migration déjà appliquée.

Le modèle protège le contrat d’entrée avant même que le contrôleur ne transforme la requête en ligne de base de données.

Valider une requête avant de la persister

Créez une Form Request réutilisable par la création et la modification :

php artisan make:request ProgramRequest

Dans app/Http/Requests/ProgramRequest.php, rendez explicites l’autorisation et les règles :

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ProgramRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:120'],
            'description' => ['required', 'string', 'max:5000'],
        ];
    }
}

Une requête qui omet name ou description, ou qui dépasse les tailles prévues, est rejetée avant Program::create ou update. Laravel rend alors une réponse JSON de validation avec le statut 422 lorsque le client demande application/json.

La documentation Laravel sur la validation explique le format des règles et le comportement des erreurs. Gardez les règles près du cas d’usage : le contrôleur reste consacré au flux CRUD.

Fixer la forme de la réponse avec une Resource

Générez une ressource JSON :

php artisan make:resource ProgramResource

Dans app/Http/Resources/ProgramResource.php, choisissez les champs publics :

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class ProgramResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'description' => $this->description,
            'created_at' => $this->created_at?->toISOString(),
            'updated_at' => $this->updated_at?->toISOString(),
        ];
    }
}

La Resource évite de rendre chaque colonne du modèle par défaut. Elle fixe le JSON que le client consomme et permet de modifier le stockage plus tard sans changer automatiquement le contrat de l’API. Laravel documente les Resources et les collections dans sa page Eloquent API Resources.

Une Resource transforme une ligne interne en réponse publique choisie, au lieu de laisser le modèle décider seul de ce qui sort.

Écrire le contrôleur sans mélanger les rôles

Générez le contrôleur :

php artisan make:controller ProgramController

Remplacez son contenu par les cinq actions nécessaires :

<?php

namespace App\Http\Controllers;

use App\Http\Requests\ProgramRequest;
use App\Http\Resources\ProgramResource;
use App\Models\Program;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Response;

class ProgramController extends Controller
{
    public function index(): AnonymousResourceCollection
    {
        return ProgramResource::collection(
            Program::query()->latest('id')->paginate(10)
        );
    }

    public function store(ProgramRequest $request): JsonResponse
    {
        $program = Program::create($request->validated());

        return (new ProgramResource($program))
            ->response()
            ->setStatusCode(201);
    }

    public function show(Program $program): ProgramResource
    {
        return new ProgramResource($program);
    }

    public function update(
        ProgramRequest $request,
        Program $program
    ): ProgramResource {
        $program->update($request->validated());

        return new ProgramResource($program->refresh());
    }

    public function destroy(Program $program): Response
    {
        $program->delete();

        return response()->noContent();
    }
}

Program $program active le route model binding implicite sur /programs/{program}. Un identifiant absent donne une réponse 404 sans recherche manuelle ni branche if. La pagination de l’index limite aussi le volume d’une réponse de liste à dix éléments par défaut.

Le contrôleur de ressource Laravel et le binding des modèles dans le routage donnent les conventions utilisées ici.

Protéger les routes avec un token Sanctum

Ajoutez le trait Sanctum au modèle utilisateur dans app/Models/User.php :

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    // Le reste du modèle User reste inchangé.
}

Pour un test local, exposez une route d’émission de token dans routes/api.php. Le projet réel peut la remplacer par son flux d’inscription et de connexion :

<?php

use App\Http\Controllers\ProgramController;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\ValidationException;

Route::post('/tokens', function (Request $request) {
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required', 'string'],
        'device_name' => ['required', 'string', 'max:100'],
    ]);

    $user = User::query()
        ->where('email', $credentials['email'])
        ->first();

    if (!$user || !Hash::check($credentials['password'], $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['Les identifiants ne sont pas valides.'],
        ]);
    }

    return [
        'token' => $user->createToken(
            $credentials['device_name']
        )->plainTextToken,
    ];
});

Route::middleware('auth:sanctum')
    ->apiResource('programs', ProgramController::class);

La route /tokens rend le token une seule fois. Les appels suivants l’envoient dans Authorization: Bearer .... Pour une application qui sépare les utilisateurs et leurs programmes, ajoutez une policy et vérifiez l’ability du token dans le contrôleur ou le middleware adapté.

Le middleware authentifie l’appelant, tandis que la policy relie cet appelant à la ressource qu’il demande.

Vérifier les cinq opérations avec curl

Démarrez le serveur de développement :

php artisan serve

Dans un second terminal, demandez un token à un utilisateur existant, puis exportez sa valeur :

TOKEN=$(curl -sS -X POST http://127.0.0.1:8000/api/tokens \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{"email":"alice@example.test","password":"mot-de-passe","device_name":"terminal"}' \
  | jq -r '.token')

printf '%s\n' "$TOKEN"

Créez ensuite un programme. Le statut attendu est 201 et la réponse contient une clé data fournie par la Resource :

curl -i -sS -X POST http://127.0.0.1:8000/api/programs \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{"name":"Atelier","description":"Suivre les routes et les réponses"}'

Relevez l’identifiant renvoyé dans data.id, puis lisez la liste et la fiche :

PROGRAM_ID=1

curl -i -sS \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json' \
  http://127.0.0.1:8000/api/programs

curl -i -sS \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json' \
  http://127.0.0.1:8000/api/programs/$PROGRAM_ID

Modifiez la ressource avec PATCH. Le statut attendu est 200 et le JSON doit reprendre le nom modifié :

curl -i -sS -X PATCH \
  http://127.0.0.1:8000/api/programs/$PROGRAM_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{"name":"Atelier API","description":"Réponse relue après modification"}'

Supprimez enfin l’enregistrement. Une réponse 204 No Content confirme l’absence de corps à lire :

curl -i -sS -X DELETE \
  http://127.0.0.1:8000/api/programs/$PROGRAM_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json'

La commande php artisan route:list --path=api -v permet de vérifier le résultat côté Laravel : les routes de apiResource portent les verbes de lecture, création, modification et suppression, et le middleware auth:sanctum apparaît sur les cinq actions protégées.

Un CRUD est vérifié quand le statut HTTP, la clé JSON et l’effet sur l’identifiant testé racontent la même histoire.

Lire les erreurs au lieu de les masquer

Sans en-tête Bearer valide, la route protégée répond normalement 401 Unauthorized. Avec un token valide mais une donnée absente, le binding répond 404 Not Found. Avec un payload qui échoue aux règles de ProgramRequest, la réponse JSON de validation porte 422 Unprocessable Content. Le statut indique la couche à relire avant de modifier du code.

Pour inspecter une erreur de validation, envoyez volontairement un nom vide et conservez les en-têtes :

curl -i -sS -X POST http://127.0.0.1:8000/api/programs \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{"name":"","description":"test de validation"}'

Si la réponse est 401, relisez le token et son expiration. Si elle est 422, relisez le JSON envoyé et les règles. Si elle est 404, relisez l’URL et l’identifiant. Cette séparation évite de corriger le contrôleur pour un problème situé dans l’authentification ou la route.

Ce que l’ancien tutoriel Laravel 8 change aujourd’hui

La version archivée en 2022 avait une intention claire : une table programs, une Resource, un contrôleur CRUD et des routes protégées par Sanctum. Elle ajoutait toutefois EnsureFrontendRequestsAreStateful dans Kernel.php, utilisait Validator directement dans le contrôleur et appelait Route::resource pour une API. Avec Laravel 13, install:api prépare le socle, la Form Request isole les règles et apiResource écarte les routes HTML create et edit.

Cette évolution ne change pas le besoin du lecteur : envoyer un token, écrire une ligne, la relire, la modifier puis la supprimer. Elle change l’endroit où chaque décision est rangée, ce qui rend l’exemple plus facile à maintenir et à contrôler avec route:list.

La bonne mise à jour n’efface pas l’objectif CRUD historique; elle remplace les pièces vieillies par les conventions que Laravel sert maintenant.

Poursuivre avec le terminal et HTTP

Pour relire les commandes utilisées dans le diagnostic et les chemins de fichiers, commencez par l’orientation Linux et terminal. Pour comprendre ce que votre client envoie et ce que le serveur retourne, poursuivez avec les requêtes HTTP, les réponses et leurs codes. Le dossier Linux et terminal rassemble les étapes qui entourent une application sans remplacer la documentation Laravel.

Gardez le fichier de migration, le payload JSON et la sortie de route:list dans le dépôt du projet. Vous pourrez alors comparer une modification de schéma, une erreur de validation ou un changement de middleware avec un état connu.