0
Cahier de projet · Atelier API

L'API d'Atelier.

Le guide pas-à-pas du back-end du projet fil rouge. Une étape par séance : l'objectif, les étapes, le code corrigé, et comment vérifier. À la fin, tu tiens une vraie API Laravel — et tu la branches sur le front Angular, à la place de json-server.

12 étapes · une vraie API branchée
Le projet fil rouge

Ce que tu construis

Atelier API — le back-end de clients, projets et factures : la moitié serveur de l'outil qu'on utilise en interne chez MarQenti. À la fin : une vraie API Laravel avec base de données, relations, CRUD validé, authentification, sécurité, performance et mise en ligne — qui remplace la fausse API (json-server) du cours Angular sans rien changer côté front.

Clients

une table, un modèle Eloquent, et les 5 routes REST /api/clients.

Projets

rattachés à un client (hasMany), avec un statut.

Factures

montant, payé ou non — le CA total calculé côté serveur.

Le contrat avec le front

Pendant le cours Angular, le front parlait à json-server (une fausse API qui lit db.json). Ici tu construis la vraie API — mêmes chemins (/clients, /projets, /factures), même forme JSON. Résultat : à la dernière étape, on ne change qu'une URL côté Angular, et json-server disparaît.

Comment l'utiliser

Après chaque séance, ouvre l'étape correspondante, fais-la, et vérifie avec la case « Vérification ». Ne saute pas d'étape — chacune construit sur la précédente. Le code est la correction : essaie d'abord, compare ensuite.

Cours 1 · Les fondations

Les fondations

Étape 1 · Séance 1

Créer l'API Laravel

Objectif

Avoir un projet Laravel « atelier-api » qui tourne sur ta machine, avec le mode API activé dès le départ.

Étapes
  1. Vérifie PHP : php -v (8.2+) et composer -V. Sinon installe-les.
  2. Crée le projet avec Composer.
  3. Active les routes API (crée routes/api.php + Sanctum), puis lance le serveur.
Le code (correction)
terminalcomposer create-project laravel/laravel atelier-api
cd atelier-api
php artisan install:api   # crée routes/api.php + installe Sanctum
php artisan serve      # → http://localhost:8000
Vérification
  • localhost:8000 affiche la page d'accueil Laravel.
  • routes/api.php existe ; GET /api/user répond 401 sans token — c'est normal, la route est protégée.
Étape 2 · Séance 2

MVC & la première route JSON

Objectif

Comprendre le trajet d'une requête — route → contrôleur → réponse JSON — et la différence entre web.php et api.php.

Étapes
  1. Génère un contrôleur.
  2. Déclare une route dans routes/api.php.
  3. Renvoie un tableau : Laravel le sérialise en JSON tout seul.
Le code (correction)
terminalphp artisan make:controller ClientController
routes/api.phpuse App\Http\Controllers\ClientController;

Route::get('/clients', [ClientController::class, 'index']);
app/Http/Controllers/ClientController.phppublic function index()
{
    return response()->json([
        ['id' => 1, 'nom' => 'Boulangerie Zitoun'],
    ]);
}
Vérification
  • GET localhost:8000/api/clients renvoie le JSON (statut 200, en-tête Content-Type: application/json).
  • Tu sais expliquer : api.php est préfixé par /api et renvoie des données ; web.php renvoie des pages.
Étape 3 · Séance 3

Routes de ressource & codes de statut

Objectif

Exposer les cinq points d'entrée REST d'une ressource proprement, avec les bons verbes HTTP et les bons codes de statut.

Étapes
  1. Régénère le contrôleur en contrôleur de ressource API (--api).
  2. Déclare la ressource en une ligne avec apiResource.
  3. Renvoie les codes qui comptent : 201 (créé), 204 (supprimé), 404 (introuvable).
Le code (correction)
terminalphp artisan make:controller ClientController --api
routes/api.phpRoute::apiResource('clients', ClientController::class);
// → GET /clients · POST /clients · GET /clients/{id}
//   PUT/PATCH /clients/{id} · DELETE /clients/{id}
les codes de statut qui comptentreturn response()->json($client, 201);   // créé
return response()->noContent();          // 204, après delete
abort(404);                            // introuvable
Vérification
  • php artisan route:list montre les cinq routes clients.
  • Un POST réussi renvoie 201 ; un GET sur un id inconnu renvoie 404.
Étape 4 · Séance 4

Laravel en API pure

Objectif

Comprendre la différence entre une vue Blade (HTML rendu serveur) et une API (JSON pour une SPA) — et renvoyer les clients au même format que json-server, pour qu'Angular ne voie aucune différence.

Étapes
  1. Garde web.php pour une page d'accueil simple, api.php pour les données.
  2. Renvoie une liste de clients aux mêmes clés que db.json.
  3. Note que le client de cette API, c'est le front Angular.
Le code (correction)
web.php vs api.php// web.php — une VUE (HTML rendu serveur, Blade)
Route::get('/', fn () => view('welcome'));

// api.php — des DONNÉES (JSON pour la SPA Angular)
Route::get('/clients', fn () => [
    ['id' => 1, 'nom' => 'Boulangerie Zitoun',
     'email' => 'z@ex.tn', 'telephone' => '22 000 111'],
]);   // Laravel sérialise le tableau en JSON
Vérification
  • GET /api/clients renvoie exactement la forme attendue par Angular (mêmes clés que db.json).
  • Tu sais dire pourquoi Atelier n'utilise pas Blade côté produit : le front, c'est Angular ; Laravel ne sert que des données.
Cours 2 · Les données

Les données

Étape 5 · Séance 1

Base de données & migrations

Objectif

Une base SQLite branchée, et un schéma versionné par des migrations pour clients, projets et factures.

Étapes
  1. Choisis SQLite dans .env (le plus simple pour démarrer) et crée le fichier.
  2. Crée une migration par table.
  3. Décris les colonnes, puis migre.
Le code (correction)
.env — la connexionDB_CONNECTION=sqlite
# commente DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD
terminaltouch database/database.sqlite
php artisan make:migration create_clients_table
php artisan make:migration create_projets_table
php artisan make:migration create_factures_table
database/migrations/..._create_clients_table.phpSchema::create('clients', function (Blueprint $table) {
    $table->id();
    $table->string('nom');
    $table->string('email');
    $table->string('telephone');
    $table->timestamps();
});
Vérification
  • php artisan migrate crée les tables sans erreur.
  • Le fichier database/database.sqlite existe ; les trois tables sont là.
Étape 6 · Séance 2

Eloquent & les relations

Objectif

Un modèle par table, et les relations client → projets / factures — pour écrire du PHP au lieu du SQL.

Étapes
  1. Génère les trois modèles.
  2. Déclare hasMany côté client, belongsTo côté enfant.
  3. Ajoute la clé étrangère dans les migrations projets & factures, et autorise l'assignation de masse ($fillable).
Le code (correction)
terminalphp artisan make:model Projet
php artisan make:model Facture   # (Client existe déjà)
app/Models/Client.phpclass Client extends Model
{
    protected $fillable = ['nom', 'email', 'telephone'];

    public function projets()  { return $this->hasMany(Projet::class); }
    public function factures() { return $this->hasMany(Facture::class); }
}
app/Models/Projet.php + la migration// modèle
protected $fillable = ['client_id', 'titre', 'statut'];
public function client() { return $this->belongsTo(Client::class); }

// dans la migration projets ET factures :
$table->foreignId('client_id')->constrained()->cascadeOnDelete();
Vérification
  • Dans php artisan tinker : Client::create([...]) puis $client->projets renvoie une collection.
  • Une facture connaît son client via $facture->client.
Étape 7 · Séance 3

Seeders & factories

Objectif

Remplir la base de données de démo réalistes en une seule commande — pour développer, démontrer et tester.

Étapes
  1. Crée une factory par modèle.
  2. Dans le seeder, génère des clients avec leurs projets et factures liés.
  3. Lance migrate:fresh --seed.
Le code (correction)
terminalphp artisan make:factory ClientFactory --model=Client
database/factories/ClientFactory.phppublic function definition(): array
{
    return [
        'nom'       => fake()->company(),
        'email'     => fake()->safeEmail(),
        'telephone' => fake()->numerify('## ### ###'),
    ];
}
database/seeders/DatabaseSeeder.phpClient::factory(20)
    ->has(Projet::factory()->count(3))
    ->has(Facture::factory()->count(5))
    ->create();
terminalphp artisan migrate:fresh --seed   # remet à zéro + remplit
Vérification
  • Après le seed, Client::count() renvoie 20 ; chacun a des projets et des factures.
  • La base est assez remplie pour rendre visibles, plus tard, le N+1 et la pagination.
Étape 8 · Séance 4

CRUD complet & validation

Objectif

Les cinq opérations branchées sur la vraie base, avec une entrée validée (Form Request) et une sortie JSON façonnée (API Resource).

Étapes
  1. Lie le contrôleur de ressource au modèle Client.
  2. Valide l'entrée avec une Form Request.
  3. Façonne la sortie avec une API Resource.
Le code (correction)
terminalphp artisan make:request StoreClientRequest
php artisan make:resource ClientResource
app/Http/Requests/StoreClientRequest.phppublic function rules(): array
{
    return [
        'nom'       => ['required', 'string', 'max:255'],
        'email'     => ['required', 'email'],
        'telephone' => ['required', 'string'],
    ];
}
ClientController.php — créer & listerpublic function store(StoreClientRequest $request)
{
    $client = Client::create($request->validated());
    return new ClientResource($client);   // 201 + JSON propre
}

public function index()
{
    return ClientResource::collection(Client::all());
}
Vérification
  • Un POST invalide (email absent) renvoie 422 avec les messages d'erreur.
  • Un POST valide crée le client et renvoie le JSON de la Resource.
  • L'API expose désormais exactement les routes que json-server servait au front.
Cours 3 · Niveau expert

Niveau expert

Étape 9 · Séance 1

Authentification (Sanctum)

Objectif

Une connexion qui renvoie un token, et des routes protégées — exactement le token que l'intercepteur Angular envoie.

Étapes
  1. Sanctum est déjà installé (étape 1). Crée un AuthController.
  2. login vérifie les identifiants et renvoie un token.
  3. Protège les routes de ressource avec auth:sanctum.
Le code (correction)
app/Http/Controllers/AuthController.phppublic function login(Request $request)
{
    $request->validate(['email' => 'required|email', 'password' => 'required']);

    $user = User::where('email', $request->email)->first();
    if (! $user || ! Hash::check($request->password, $user->password)) {
        return response()->json(['message' => 'Identifiants invalides'], 401);
    }
    return ['token' => $user->createToken('atelier')->plainTextToken];
}
routes/api.php — protéger la ressourceRoute::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('clients', ClientController::class);
    Route::get('/user', fn (Request $r) => $r->user());
});
Vérification
  • POST /api/login avec de bons identifiants renvoie un token ; sinon 401.
  • Appeler /api/clients sans l'en-tête Authorization: Bearer … renvoie 401 ; avec, ça passe.
Étape 10 · Séance 2

CORS, throttle & rate limiting

Objectif

Laisser passer le front Angular (CORS) et limiter les abus — un plafond strict sur le login pour couper le brute-force.

Étapes
  1. Publie et règle la config CORS pour autoriser l'origine du front.
  2. Applique un limiteur de débit strict sur /login.
  3. Comprends la réponse 429 Too Many Requests.
Le code (correction)
terminal — publier la config CORSphp artisan config:publish cors
config/cors.php — autoriser Angular'paths' => ['api/*', 'login'],
'allowed_methods' => ['*'],
'allowed_origins' => [
    'http://localhost:4200',   // le dev Angular
    'https://atelier.tn',      // le front en prod
],
routes/api.php — limiter le loginRoute::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:5,1');   // 5 essais/min → 429 ensuite
Vérification
  • Depuis Angular (localhost:4200), les requêtes passent — aucune erreur CORS dans la console.
  • Marteler /login renvoie 429 après cinq essais dans la minute.
Étape 11 · Séance 3

Performance : N+1, pagination, index

Objectif

Une API qui reste rapide en grandissant : tuer le N+1 avec l'eager loading, paginer, et indexer les clés étrangères.

Étapes
  1. Eager-load les relations dans index pour tuer le N+1.
  2. Pagine la liste au lieu de tout renvoyer.
  3. Indexe client_id sur projets et factures.
Le code (correction)
ClientController@index — 2 requêtes, paginépublic function index()
{
    return ClientResource::collection(
        Client::with(['projets', 'factures'])->paginate(15)
    );
}
migration — indexer la clé étrangèreSchema::table('projets', function (Blueprint $table) {
    $table->index('client_id');   // recherches par client → instantanées
});
Vérification
  • Laravel Debugbar montre 2 requêtes au lieu de 20+ pour la liste des clients.
  • La réponse est paginée (data, meta, links) — ce que le front sait déjà lire.
Étape 12 · Séance 4

Déployer & brancher Angular — l'épreuve finale

Objectif

Mettre l'API en ligne, puis pointer le front Angular dessus et retirer json-server. Le produit full-stack, livré.

Étapes
  1. Prépare la prod : .env de production, APP_ENV=production, APP_DEBUG=false.
  2. Héberge l'API en HTTPS (VPS, Forge…) ; migre la base sur le serveur.
  3. Dans Angular, change environment.prod.ts → l'URL de la vraie API.
  4. Supprime json-server : il ne sert plus à rien.
Le code (correction)
sur le serveur de prodphp artisan migrate --force   # migre sans confirmation interactive
php artisan config:cache
php artisan route:cache
ANGULAR — src/environments/environment.prod.tsexport const environment = {
  production: true,
  apiUrl: 'https://api.atelier.tn/api',   // la VRAIE API Laravel
};
// json-server : supprimé. Le contrat JSON n'a pas changé.
Vérification — le projet full-stack est fini si…
  • L'API Laravel répond en HTTPS à une vraie URL.
  • Le front Angular liste, crée et cherche des clients via l'API Laravel, sans json-server.
  • Le même code Angular fonctionne — on n'a changé que l'apiUrl.
  • Login → token → l'intercepteur envoie Authorization → l'API protège ses routes.