Objectifs de cette leçon
- Intégrer Blade templates
- Utiliser Inertia.js pour le SPA
- Styler avec Tailwind CSS
Consommer l'API Laravel depuis React ⚛️
Architecture fullstack
┌─────────────┐ HTTP/JSON ┌─────────────┐ SQL ┌──────────┐ │ React │ ────────────────→ │ Laravel │ ──────────→ │ BDD │ │ (Frontend) │ ←──────────────── │ (API) │ ←────────── │ (MySQL) │ └─────────────┘ └─────────────┘ └──────────┘ Port 3000 Port 8000
Le frontend React tourne sur le port 3000 (Vite). Laravel tourne sur le port 8000 (artisan serve). Ils communiquent via HTTP en JSON.
1. Configurer CORS 🌐
CORS (Cross-Origin Resource Sharing) est un mécanisme de sécurité du navigateur. Sans CORS, le navigateur bloque les requêtes du frontend vers un domaine différent.
config/cors.php
<?php return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], // 👇 Le domaine de ton frontend 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], 'allowed_headers' => ['*'], 'supports_credentials' => true, ];
⚠️ En production, remplace
*par les méthodes et origines exactes.
2. Configurer le proxy Vite ⚡
Pendant le développement, configure un proxy dans vite.config.ts pour éviter les problèmes CORS :
// vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8000', // Serveur Laravel changeOrigin: true, }, }, }, })
Avec ce proxy, tu peux faire fetch('/api/posts') au lieu de fetch('http://localhost:8000/api/posts').
3. GET — Récupérer les données 📥
Avec fetch (natif)
import { useState, useEffect } from 'react'; interface Post { id: number; title: string; body: string; author: { name: string }; } function PostList() { const [posts, setPosts] = useState<Post[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/posts') .then(res => { if (!res.ok) throw new Error('Erreur réseau'); return res.json(); }) .then(data => { setPosts(data); setLoading(false); }) .catch(err => { console.error(err); setLoading(false); }); }, []); if (loading) return <div>Chargement...</div>; return ( <div className="grid gap-4"> {posts.map(post => ( <div key={post.id} className="card"> <h3>{post.title}</h3> <p>{post.body}</p> <small>Par {post.author?.name}</small> </div> ))} </div> ); }
Avec axios (recommandé ✅)
npm install axios
import axios from 'axios'; useEffect(() => { axios.get('/api/posts') .then(res => setPosts(res.data)) // res.data = déjà parsé .catch(err => console.error(err.message)); }, []);
💡 Avec axios, pas besoin d'appeler
.json()— la réponse est déjà parsée.
4. POST — Créer une ressource ➕
async function handleCreate(data: { title: string; body: string }) { try { const res = await fetch('/api/posts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify(data), }); if (!res.ok) { const err = await res.json(); // err.errors = { title: ["Le titre est requis"] } setErrors(err.errors); return; } const newPost = await res.json(); setPosts(prev => [newPost, ...prev]); // Ajoute en tête de liste setErrors({}); } catch (err) { console.error('Erreur réseau', err); } }
Points clés :
Content-Type: application/jsonest OBLIGATOIRE- Toujours gérer les erreurs de validation (422)
- Ajoute le nouvel élément à la liste localement
5. PUT — Modifier une ressource ✏️
async function handleUpdate(id: number, data: Partial<Post>) { try { const res = await fetch('/api/posts/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (!res.ok) throw new Error('Erreur modification'); const updated = await res.json(); // Remplace l'élément dans la liste setPosts(prev => prev.map(post => post.id === id ? updated : post) ); } catch (err) { console.error(err); } }
6. DELETE — Supprimer une ressource 🗑️
async function handleDelete(id: number) { if (!window.confirm('Supprimer cet article ?')) return; try { const res = await fetch('/api/posts/' + id, { method: 'DELETE', }); if (res.status === 204) { // Retire de la liste sans recharger setPosts(prev => prev.filter(post => post.id !== id)); } } catch (err) { console.error(err); } }
💡 Le code 204 = No Content. Il signifie "succès, rien à retourner".
7. Authentification avec Bearer token 🔐
Intercepteur axios
// lib/axios.ts import axios from 'axios'; const api = axios.create({ baseURL: '/api', headers: { 'Accept': 'application/json' }, }); // Intercepteur : ajoute le token à chaque requête api.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = 'Bearer ' + token; } return config; }); // Intercepteur : gère les erreurs 401 api.interceptors.response.use( response => response, error => { if (error.response?.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); } ); export default api;
Utilisation
import api from '../lib/axios'; // Route protégée — le token est ajouté automatiquement const { data: user } = await api.get('/user'); console.log(user.name); // "Alice" // Création avec token automatique const { data: post } = await api.post('/posts', { title: 'Nouvel article', body: 'Contenu...', });
8. Gestion des erreurs 🚨
// Hook personnalisé pour la gestion des erreurs function useApiError() { const [errors, setErrors] = useState<Record<string, string[]>>({}); const handleError = (err: unknown) => { if (axios.isAxiosError(err) && err.response?.status === 422) { // Erreurs de validation Laravel setErrors(err.response.data.errors); } else { // Erreur réseau ou serveur console.error('Erreur inattendue', err); } }; return { errors, setErrors, handleError }; }
Bonnes pratiques ✅
- Utiliser un proxy Vite en développement pour éviter CORS
- Toujours configurer CORS correctement en production
- Utiliser axios plutôt que fetch pour les apps complexes (intercepteurs)
- Gérer les erreurs de manière centralisée (intercepteur 401)
- Ne jamais exposer le token dans l'URL ou les logs
- Utiliser TypeScript pour typer les réponses de l'API
- Ajouter un loading state pour chaque requête
- Mettre à jour l'UI localement après POST/PUT/DELETE (pas de rechargement)
Pièges fréquents 🚨
| Piège | Solution |
|---|---|
❌ Oublier Content-Type: application/json | Laravel ne parse pas le body → erreur |
| ❌ Ne pas gérer 401 | L'utilisateur reste sur une page sans données |
| ❌ Ne pas gérer 422 | Les erreurs de validation ne s'affichent pas |
| ❌ Recharger toute la page après une action | Mettre à jour l'état local React |
| ❌ Oublier le proxy Vite | Erreur CORS en développement |
| ❌ Stocker le token dans sessionStorage | Perdu si l'onglet est fermé → préfère localStorage |
Exercices pour toi 🎯
- Pratique : Crée un composant React
PostListqui affiche les articles depuis/api/posts. - Formulaire : Crée un formulaire
CreatePostavec soumission POST. Affiche les erreurs de validation. - Modification : Ajoute un bouton "Modifier" sur chaque article. Ouvre un formulaire pré-rempli avec PUT.
- Suppression : Ajoute un bouton "Supprimer" avec confirmation et DELETE.
- Défi : Ouvre le simulateur ci-dessous. Parcours les 7 étapes pour voir le code Laravel et React côte à côte pour chaque opération.
⚡ Simulateur — Consommer l'API Laravel depuis React
Étape 1/7// config/cors.php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
'allowed_headers' => ['*'],
'supports_credentials' => true,
];// pas de code React nécessaire côté CORS
Sans CORS, le navigateur bloque les requêtes cross-origin. Laravel gère ça dans config/cors.php.