0 XP
?
API REST et Backend avec Laravel/Consommer l'API avec React et Next.js
Avancé70 min50 XP

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/json est 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 ✅

  1. Utiliser un proxy Vite en développement pour éviter CORS
  2. Toujours configurer CORS correctement en production
  3. Utiliser axios plutôt que fetch pour les apps complexes (intercepteurs)
  4. Gérer les erreurs de manière centralisée (intercepteur 401)
  5. Ne jamais exposer le token dans l'URL ou les logs
  6. Utiliser TypeScript pour typer les réponses de l'API
  7. Ajouter un loading state pour chaque requête
  8. Mettre à jour l'UI localement après POST/PUT/DELETE (pas de rechargement)

Pièges fréquents 🚨

PiègeSolution
❌ Oublier Content-Type: application/jsonLaravel ne parse pas le body → erreur
❌ Ne pas gérer 401L'utilisateur reste sur une page sans données
❌ Ne pas gérer 422Les erreurs de validation ne s'affichent pas
❌ Recharger toute la page après une actionMettre à jour l'état local React
❌ Oublier le proxy ViteErreur CORS en développement
❌ Stocker le token dans sessionStoragePerdu si l'onglet est fermé → préfère localStorage

Exercices pour toi 🎯

  1. Pratique : Crée un composant React PostList qui affiche les articles depuis /api/posts.
  2. Formulaire : Crée un formulaire CreatePost avec soumission POST. Affiche les erreurs de validation.
  3. Modification : Ajoute un bouton "Modifier" sur chaque article. Ouvre un formulaire pré-rempli avec PUT.
  4. Suppression : Ajoute un bouton "Supprimer" avec confirmation et DELETE.
  5. 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
📝 Pseudo-code
1# Consommer l'API Laravel depuis React
2// 1. Configurer CORS (config/cors.php) # ⬅ CORS
3fetch('http://api.mon-app.test/api/posts') # ⬅ GET — lister
4axios.get('/api/posts').then(res => setPosts(...)) # ⬅ GET avec axios
5fetch('/api/posts', { method:'POST', body: JSON... }) # ⬅ POST — créer
6fetch('/api/posts/1', { method:'PUT', body: JSON... }) # ⬅ PUT — modifier
7fetch('/api/posts/1', { method:'DELETE' }) # ⬅ DELETE — supprimer
8headers: { Authorization: 'Bearer ' + token } # ⬅ requête authentifiée
⚡ React ↔ Laravel
🌐 CORS📥 GET🖥️ Affichage POST✏️ PUT🗑️ DELETE🔐 Auth
Permettre au frontend (localhost:3000) d'appeler le backend (localhost:8000)
🐘 Laravel (API)
// config/cors.php
return [
  'paths' => ['api/*', 'sanctum/csrf-cookie'],
  'allowed_methods' => ['*'],
  'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
  'allowed_headers' => ['*'],
  'supports_credentials' => true,
];
⚛️ React (Frontend)
// pas de code React nécessaire côté CORS
💡 En développement, Laravel et React tournent sur des ports différents → CORS nécessaire.
CORS (Cross-Origin Resource Sharing) est un mécanisme de sécurité. Laravel doit autoriser le domaine du frontend.
💡 Architecture fullstack
⚛️ React🌐 CORS🐘 Laravel🗄️ BDD
Le frontend React envoie des requêtes HTTP (fetch/axios) vers l'API Laravel qui répond en JSON. CORS doit être configuré pour autoriser le domaine du frontend.

Sans CORS, le navigateur bloque les requêtes cross-origin. Laravel gère ça dans config/cors.php.