Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
10 novembre 2025

Comment créer un SAAS sur Laravel 12 quand on est débutant ? (gros noob !)

Créer un SaaS (Software as a Service) multi-utilisateurs peut sembler intimidant pour un débutant, mais Laravel 12 facilite grandement les choses. Laravel est un framework PHP moderne et progressif : il s’adapte aux débutants tout en offrant la puissance nécessaire aux projets avancés. Laravel 12, avec sa syntaxe élégante et ses fonctionnalités récentes, offre une […]

27 min de lecture
19 vues
réactions
Partager :
Illustration de développement web avec Laravel.

Créer un SaaS (Software as a Service) multi-utilisateurs peut sembler intimidant pour un débutant, mais Laravel 12 facilite grandement les choses. Laravel est un framework PHP moderne et progressif : il s’adapte aux débutants tout en offrant la puissance nécessaire aux projets avancés. Laravel 12, avec sa syntaxe élégante et ses fonctionnalités récentes, offre une base solide pour développer des applications multi-locataires (multi-tenants) évolutives, maintenables et sécurisées. Dans ce tutoriel, nous allons créer pas à pas une application SaaS simple sur Laravel 12. L’exemple concret choisi est une plateforme de gestion d’avis sur des commerces, un peu comme un mini Google My Business.

Nous couvrirons toutes les étapes essentielles : installation de Laravel 12, configuration de la base de données MySQL, mise en place de l’authentification, création des modèles (Business, Review) avec leurs migrations, définition des relations multi-locataires (une seule base de données partagée par tous les clients), développement des contrôleurs et vues Blade pour l’interface utilisateur, et enfin quelques tests du fonctionnement. Chaque étape sera illustrée par les commandes Artisan ou Composer nécessaires, avec des explications claires pour un « gros noob » en Laravel 😉.

Prérequis : avoir PHP (>= 8.2), Composer et MySQL installés sur votre machine, et idéalement quelques notions de base en PHP orienté objet.

Étape 1 : Installation de Laravel 12 et configuration de base

Créer un nouveau projet Laravel 12

Dans un terminal, naviguez vers votre dossier de travail et exécutez la commande de création de projet Laravel. Deux options s’offrent à vous : utiliser l’installeur Laravel ou Composer. Par exemple, avec Composer vous pouvez taper :

composer create-project laravel/laravel:^12.0 saas-app

Cette commande va télécharger Laravel 12 et créer un nouveau dossier saas-app. Vous pouvez aussi utiliser l’installeur Laravel après l’avoir installé via

composer global require laravel/installer

en tapant par exemple

laravel new saas-app.

Une fois le projet créé, entrez dans le dossier :

cd saas-app.

Configurer l’environnement (.env)

Laravel a généré un fichier .env à la racine du projet. Dupliquez éventuellement le fichier .env.example en .env si ce n’est pas déjà fait, puis ouvrez-le pour y définir les paramètres de connexion à votre base de données MySQL.

Par exemple :

DB_DATABASE=saas_app_db
DB_USERNAME=root
DB_PASSWORD=secret

Assurez-vous d’avoir créé une base de données (ici nommée saas_app_db) via MySQL. Mettez à jour DB_HOST, DB_PORT si nécessaire (par défaut 127.0.0.1:3306 convient). Sauvegardez le fichier. Laravel chargera ces réglages au démarrage de l’application.

Installer les dépendances front-end

Laravel 12 inclut Vite pour la compilation des assets front (CSS/JS). Pour l’instant, installez les dépendances Node.js :

npm install npm run build

(Cette étape prépare les assets de l’interface. Vous pourrez relancer

npm run dev

en parallèle du serveur PHP pour du rechargement à chaud durant le développement.)

Lancer le serveur de développement

Démarrez le serveur Laravel pour vérifier que tout va bien :

php artisan serve

Ouvrez votre navigateur sur http://localhost:8000 et vous devriez voir la page d’accueil Laravel par défaut. À ce stade, l’installation de base est réussie.

Étape 2 : Mise en place de l’authentification (Laravel Breeze)

Pour notre application SaaS, nous aurons besoin d’une authentification utilisateur (les commerçants doivent pouvoir se connecter pour gérer leur fiche, et les clients pour laisser des avis). Laravel 12 propose de nouveaux Starter Kits (React, Vue, Livewire), mais pour une approche simple avec Blade (sans framework JavaScript lourd) on va utiliser Laravel Breeze, qui reste compatible avec Laravel 12. Breeze va générer tout le boilerplate d’authentification (pages de login, inscription, mot de passe oublié, etc.) en Blade + Tailwind CSS.

Installer Laravel Breeze

Dans le terminal du projet, exécutez :

composer require laravel/breeze --dev 
php artisan breeze:install

Lors de l’exécution de breeze:install on vous proposera de choisir le stack front-end. Sélectionnez l’option Blade (interface Blade + Tailwind, sans Livewire/Inertia). Une fois terminé, Breeze aura ajouté les vues Blade d’authentification, les routes correspondantes, ainsi qu’une page Dashboard par défaut pour l’utilisateur connecté.

Compiler les assets et migrer

Après l’installation de Breeze, recompilez les assets front :

npm run build

ou

npm run dev

Puis exécutez les migrations pour générer les tables par défaut d’auth (users, password_resets, etc.) :

php artisan migrate

Cela va créer une table users avec les colonnes usuelles (name, email, password…) et d’autres tables liées à l’authentification. Vous avez maintenant un système d’inscription/connexion opérationnel. Testez-le rapidement : rendez-vous sur http://localhost:8000/register pour créer un compte utilisateur, puis /login pour vérifier la connexion. Vous devriez accéder à /dashboard une fois logué.

Remarque : Laravel Breeze est optionnel, mais il vous fait gagner beaucoup de temps. Sans Breeze, il faudrait créer manuellement les contrôleurs d’auth, les vues Blade correspondantes et configurer les routes : un travail déjà fait par Breeze. En Laravel 12, Breeze n’est plus proposé directement par la commande laravel new, mais on peut toujours l’ajouter ensuite (comme on vient de le faire).

Étape 3 : Architecture SaaS multi-tenant à base de données unique

Avant de coder les fonctionnalités métier, prenons un moment pour réfléchir à l’architecture multi-tenant de notre application. Qui sont les « tenants » (locataires) dans notre SaaS Avis-Commerces ? Ici, le tenant correspond à un commerçant (une entreprise) qui s’inscrit sur la plateforme pour gérer sa page et consulter ses avis. Chaque commerce correspondra donc à une entité « Business » dans notre application. Les avis laissés par les clients seront liés à ces Business.

Nous avons choisi une approche single database, c’est-à-dire une seule base de données MySQL partagée pour tous les tenants. Il faut donc structurer les tables de manière à isoler les données de chaque tenant via un identifiant de tenant. En pratique, dans un scénario multi-locataire sur base de données unique, presque chaque table possède un champ identifiant le tenant (par ex. tenant_id) pour distinguer les enregistrements de chaque client. Dans notre cas, le rôle de tenant_id sera joué par business_id dans la table des avis, afin de savoir à quel commerce appartient chaque avis. De même, il nous faudra un moyen de lier les utilisateurs commerçants à leur commerce (par exemple via un champ user_id dans la table des commerces).

Résumé de l’approche multi-locataire (une base) :

  • Une table businesses pour les commerces (tenants). Chaque commerce a un propriétaire (un utilisateur), et un ID unique qui servira de clé de liaison.
  • Une table reviews pour les avis. Chaque avis est lié à un commerce (business_id) et à l’utilisateur auteur de l’avis (user_id).
  • Les utilisateurs (users) peuvent être soit des propriétaires de commerce, soit de simples clients qui laissent des avis (ou les deux). On pourra distinguer les deux rôles via la relation qu’un utilisateur entretient avec un commerce (ex : un user qui a un commerce est un propriétaire).

Avec ce design, toutes les données sont dans les mêmes tables mais bien identifiées par commerce. On veillera dans le code à n’afficher à un commerçant que les avis de son commerce, et à empêcher qu’il accède aux données d’un autre commerce. Ce principe d’isolation logique des données renforce la sécurité d’un SaaS multi-clients. (Notons qu’il existe des packages Laravel spécialisés – Spatie Laravel Multitenancy, stancl/tenancy, etc. – qui automatisent ce genre de filtrage via des scopes globaux, mais pour comprendre le fonctionnement, nous allons tout faire manuellement dans ce tutoriel.)

Étape 4 : Création des modèles et migrations (Business, Review)

Passons à la construction de notre base de données et de nos modèles Eloquent. Nous allons créer deux nouvelles entités principales : Business (commerce) et Review (avis).

Migration et modèle Business

Générez le modèle Eloquent Business ainsi que sa migration :

php artisan make:model Business -m

Cette commande crée le fichier de modèle app/Models/Business.php et une migration vide nommée par ex. 2025_11_10_######_create_businesses_table.php dans database/migrations/.

Ouvrez cette migration et définissez y les colonnes de la table businesses :

Schema::create('businesses', function (Blueprint $table) { 
  $table->id(); $table->string('name'); // nom du commerce 
  $table->text('description')->nullable(); // description du commerce 
  $table->foreignId('user_id')->constrained()->onDelete('cascade'); // propriétaire 
  $table->timestamps(); 
});

Ici on crée un champ user_id qui est une foreign key vers la table users (Laravel crée par défaut la table users via Breeze). Le onDelete('cascade') signifie que si l’utilisateur propriétaire est supprimé, on supprimera aussi son commerce (à adapter selon vos règles métiers, mais c’est une option raisonnable). Un commerce a un nom, une description (facultative), et Laravel gère pour nous id (clé primaire auto-incr.) et les timestamps.

Ensuite, dans le modèle Business (app/Models/Business.php), ajoutez les relations Eloquent suivantes :

class Business extends Model { 

  protected $fillable = ['name','description','user_id']; 
  
  // Relation: un commerce appartient à un utilisateur (propriétaire) 
  public function owner() { return $this->belongsTo(User::class, 'user_id'); } 
  
  // Relation: un commerce possède plusieurs avis 
  public function reviews() { return $this->hasMany(Review::class);} 
  
}

On utilise owner() pour la lisibilité (au lieu de user) et on précise la clé étrangère. La relation reviews() permettra de récupérer tous les avis liés à un commerce.

Migration et modèle Review

Générez de même le modèle Review et sa migration :

php artisan make:model Review -m

Puis éditez la migration créée create_reviews_table.php :

Schema::create('reviews', function (Blueprint $table) { 
  $table->id(); $table->foreignId('business_id')->constrained()->onDelete('cascade'); 
  $table->foreignId('user_id')->constrained()->onDelete('cascade'); 
  $table->tinyInteger('rating')->unsigned(); 
  $table->text('comment'); 
  $table->timestamps(); 
});

Cette table lie chaque avis à un commerce (business_id) et à l’utilisateur auteur (user_id). Ici j’ai choisi un champ rating de type entier court (0-255) pour stocker la note (on pourra décider que 1–5 signifie une étoile à cinq étoiles). Le comment contiendra le texte de l’avis. On cascade la suppression sur business_id et user_id pour nettoyer les avis si un commerce ou un utilisateur est supprimé (à réfléchir selon les besoins réels, mais pour l’exemple c’est acceptable).

Dans le modèle Review (app/Models/Review.php), définissez les relations inverses :

class Review extends Model { 

  protected $fillable = ['business_id','user_id','rating','comment']; 
  
  // Relation: cet avis concerne un commerce 
  public function business() { return $this->belongsTo(Business::class); } 
  
  // Relation: cet avis a été écrit par un utilisateur 
  public function author() { return $this->belongsTo(User::class, 'user_id'); } 
  
}

Ici on nomme la relation utilisateur author() pour plus de clarté dans le code (sinon user()).

Mettre à jour le modèle User

Ouvrez app/Models/User.php.

On va ajouter deux relations utiles :

class User extends Authenticatable { 

  //... autres traits et propriétés 
  
  // Relation: un utilisateur peut posséder un commerce (un seul) 
  public function business() { return $this->hasOne(Business::class); } 
  
  // Relation: un utilisateur peut écrire plusieurs avis 
  public function reviews() { return $this->hasMany(Review::class); } 
  
}

Ainsi, $user->business renverra le commerce dont il est propriétaire (ou null s’il n’en a pas), et $user->reviews la collection de ses avis rédigés.

Exécuter les migrations

À présent que nos migrations sont prêtes, on les exécute :

php artisan migrate

Cela va créer les tables businesses et reviews dans votre base MySQL, avec les colonnes définies. Vérifiez dans votre SGBD que tout est en place (structure des tables OK, clés étrangères OK). Notre modèle de données multi-locataire est opérationnel : chaque enregistrement d’avis comportera un business_id qui indique à quel tenant (commerce) il appartient.

Étape 5 : Création des contrôleurs et routes pour le SaaS

Maintenant que les tables et modèles sont en place, attaquons-nous à la logique applicative. Nous allons créer deux contrôleurs principaux : BusinessController pour gérer les commerces (tenants) et ReviewController pour gérer les avis.

Générer les contrôleurs

Utilisez les commandes Artisan :

php artisan make:controller BusinessController --resource 
php artisan make:controller ReviewController

La première génère un contrôleur resource (avec les méthodes index, create, store, show, edit, update, destroy pré-écrites). La seconde crée un contrôleur basique où on ajoutera manuellement ce qu’il faut (on n’aura qu’une méthode store et peut-être destroy pour les avis).

Implémenter BusinessController

Ouvrez app/Http/Controllers/BusinessController.php. Remplissons les méthodes essentielles :

index() : lister tous les commerces. On récupérera tous les Business pour les afficher. On peut paginer si nécessaire.

show($id) : afficher la page d’un commerce avec ses détails et avis. On utilisera l’ID (ou le modèle injecté) pour trouver le Business et charger ses reviews.

create() : afficher le formulaire de création d’un nouveau commerce. (Réservé aux utilisateurs authentifiés, idéalement ceux qui n’ont pas encore de commerce.)

store() : traiter le formulaire de création d’un commerce. Créera un nouveau Business associé à l’utilisateur connecté.

edit($id) : formulaire d’édition d’un commerce existant. (Réservé au propriétaire du commerce en question.)update($id) : traitement de la modif du commerce.

destroy($id) : éventuellement, suppression d’un commerce. (Optionnel dans notre cas, on peut l’implémenter pour être complet, en veillant aux autorisations.)Commençons par les plus importants: store et update, qui manipulent les données.

Exemple d’implémentation de store() (ajouté dans BusinessController) :

public function store(Request $request) { 
  $this->validate($request, [ 
  'name' => 'required|max:255', 
  'description' => 'nullable|string' 
  ]);
  
  $business = Business::create([
    'name' => $request->name, 
    'description' => $request->description,
    'user_id' => auth()->id()
   ]); 
   
   return redirect()->route('businesses.show', $business->id) ->with('success', 'Votre commerce a été créé avec succès!'); 
}

Ici on valide que le nom est présent (255 caractères max) et que la description, optionnelle, est bien une string. On utilise ensuite Business::create en passant un tableau d’attributs. Grâce à $fillable défini dans le modèle, Laravel peut faire l’assignation de masse.

Notez qu’on force user_id à auth()->id() pour éviter qu’un utilisateur malintentionné ne crée un commerce au nom d’un autre – on prend l’ID de l’utilisateur connecté (authentifié) comme propriétaire du commerce. On redirige ensuite vers la page du commerce nouvellement créé avec un petit message flash de succès.

Exemple d’implémentation de update() :

public function update(Request $request, Business $business) { 
  $this->authorize('update', $business); 
  
  // (voir plus bas pour Policy alternative) 
  
  $this->validate($request, [ 
    'name' => 'required|max:255', 
    'description' => 'nullable|string' 
  ]); 
  
  // Seul le propriétaire peut arriver ici (grâce à l'autorisation ci-dessus) 
  
  $business->update([ 
    'name' => $request->name, 
    'description' => $request->description 
  ]);
  
  return redirect()->route('businesses.show', $business->id) ->with('success', 'Commerce mis à jour.'); 
  
}

On a introduit une vérification d’autorisation $this->authorize('update', $business).

Cela nécessite de définir une Policy Laravel pour Business via

php artisan make:policy BusinessPolicy --model=Business

C’est un moyen élégant de centraliser la logique d’autorisation (par ex., seul le propriétaire peut éditer son commerce). Vous pouvez aussi simplement vérifier dans le contrôleur : if ($business->user_id != auth()->id()) abort(403);. À vous de voir, mais il est important d’empêcher un utilisateur lambda ou un autre commerçant de modifier un commerce qui ne lui appartient pas.

Pour edit($id) et create(), il s’agira juste de retourner les vues Blade appropriées (formulaires). Veillez aussi à y restreindre l’accès via middleware ou policy. Par exemple, create() pourrait vérifier que l’utilisateur n’a pas déjà un commerce s’il n’est autorisé qu’à en créer un seul (sinon, on pourrait autoriser plusieurs commerces par utilisateur, selon votre application).

Enfin, index() peut ressembler à :

public function index() { 

  $businesses = Business::with('reviews')->get(); 
  return view('businesses.index', compact('businesses')); 

}

Ici on récupère tous les commerces avec leurs avis (relation eager-loaded via with), ce qui permettra éventuellement d’afficher le nombre d’avis ou une moyenne. Pour une vraie app, on ajouterait de la pagination: Business::paginate(10) par ex.

Et show($id) :

public function show(Business $business) { 

  // Grâce au route-model binding, $business est le modèle correspondant à l'id 
  $business->load('reviews.author'); // charge les avis et leurs auteurs 
  return view('businesses.show', compact('business')); 
  
}

On utilise $business->load('reviews.author') pour pré-charger les auteurs de chaque avis, afin de pouvoir afficher le nom de l’auteur sans requête additionnelle dans la vue.

Implémenter ReviewController

Pour les avis, nous n’avons essentiellement besoin que d’ajouter une méthode store (et possiblement destroy). Ouvrez app/Http/Controllers/ReviewController.php et ajoutez :

public function store(Request $request, Business $business) { 

  $this->validate($request, [
    'rating' => 'required|integer|between:1,5',
    'comment' => 'required|string' 
  ]); 
  
  Review::create([
    'business_id' => $business->id,
    'user_id' => auth()->id(),
    'rating' => $request->rating,
    'comment' => $request->comment 
  ]); 
  
  return redirect()->route('businesses.show', $business->id) ->with('success', 'Merci, votre avis a bien été ajouté !');
  
}

Ici, grâce au paramètre $business dans la signature (route model binding), on sait sur quel commerce on veut poster un avis.

On valide que l’utilisateur a bien fourni une note entre 1 et 5 et un commentaire non vide. Puis on crée l’avis via Review::create en assignant le business_id et le user_id courants. On pourrait également vérifier que l’utilisateur n’a pas déjà donné un avis sur ce commerce (pour éviter les doublons); c’est une règle de gestion potentielle, non implémentée ici pour simplifier.

(Optionnel) Pour destroy(Review $review), on pourrait permettre à l’auteur d’un avis de le supprimer, ou à un admin de modérer. Si vous souhaitez l’ajouter, n’oubliez pas de vérifier l’autorisation (auteur = user courant, ou propriétaire du business concerné éventuellement) avant de faire $review->delete().

Déclarer les routes Web

Ouvrez le fichier routes/web.php. On va définir les routes resource pour Business, et une route spécifique pour la création d’avis :

use App\Http\Controllers\BusinessController; 
use App\Http\Controllers\ReviewController; 

  // Routes publiques 
  Route::get('/', function() { return redirect('/businesses'); });
  Route::resource('businesses', BusinessController::class)->only(['index','show']);
  
  // Routes protégées par auth 
  Route::middleware('auth')->group(function() { 
    Route::resource('businesses', BusinessController::class)->except(['index','show']);
    Route::post('/businesses/{business}/reviews', [ReviewController::class, 'store'])->name('businesses.reviews.store'); 
  });

Explications :

On redirige la homepage / vers la liste des commerces pour plus de commodité. On déclare Route::resource('businesses', ...) deux fois : Laravel ne permet pas de mettre une même resource à la fois publique et protégée dans une seule déclaration, donc on en fait deux en scindant les méthodes. La première expose seulement index et show sans protection (tout le monde peut voir la liste des commerces et la page d’un commerce). La seconde déclare les autres méthodes (create, store, edit, update, destroy) sous middleware auth; ainsi, si un utilisateur non connecté tente d’y accéder, il sera redirigé vers le login automatiquement (grâce au middleware auth).

Pour les avis, on définit une route POST vers /businesses/{business}/reviews pointant vers ReviewController@store. On lui donne un name explicite businesses.reviews.store. Cette route est placée dans le groupe auth, car il faut être connecté pour poster un avis. (On aurait pu aussi utiliser Route::resource sur Review en nested resource, mais pour un seul verbe autant écrire explicitement.)

Avec ces routes en place, Laravel va automatiquement injecter le modèle Business dans nos méthodes de contrôleur quand l’ID est présent dans l’URL, grâce au route model binding. Par exemple, l’URL /businesses/5 appellera BusinessController@show(Business $business) avec $business étant l’instance dont l’id =5. De même, la route d’avis appellera ReviewController@store(Request $req, Business $business) avec le Business correspondant au {business}.

(Optionnel) Autorisations via Policies

Il est recommandé d’utiliser les Policies Laravel pour gérer qui peut mettre à jour ou supprimer un commerce/avis. Par exemple, générez

php artisan make:policy BusinessPolicy --model=Business

et implémentez la méthode update(User $user, Business $business) pour retourner true seulement si $business->user_id == $user->id.

Ensuite, dans le contrôleur on peut appeler $this->authorize('update', $business) comme montré plus haut. Pour une simple application on peut s’en passer et utiliser des conditions dans le contrôleur, mais les Policies offrent une solution propre et intégrée au framework (et fonctionnent automatiquement via les Blade directives @can, etc.).

À ce stade, notre logique backend est prête : on a les endpoints pour créer et afficher des commerces et des avis, avec une protection basique. Passons maintenant à la couche présentation pour que tout cela soit utilisable par nos (futurs) clients et commerçants.

Étape 6 : Création de l’interface utilisateur en Blade

Laravel Breeze nous a déjà fourni une base d’interface avec Tailwind CSS. On va s’appuyer dessus pour créer quelques vues Blade pour nos pages : liste des commerces, détail d’un commerce, formulaire de création/édition, etc. Assurez-vous d’être à l’aise avec les bases de Blade (boucles @foreach, variables passées depuis le contrôleur, directives @csrf, etc.).

Vue liste des commerces (index)

Créez un fichier resources/views/businesses/index.blade.php. Ce sera la page d’accueil de fait. On va y afficher tous les commerces disponibles, avec peut-être un lien pour les propriétaires permettant d’ajouter un commerce.

Voici un squelette possible :

@extends('layouts.app')
@section('content’)
    <h1>Liste des commerces</h1>
    @auth
      @if (!Auth::user()->business) 
        + Ajouter mon commerce 
      @endif 
    @endauth 
      @foreach($businesses as $biz)
            <h2>{{ $biz->name }}</h2>
            {{ Str::limit($biz->description, 100) }}
            {{ $biz->reviews->count() }} avis 
            @if($biz->reviews->count())
              – Note moyenne : {{ number_format($biz->reviews->avg('rating'), 1) }}/5 
            @endif 
          Voir la fiche »
      @endforeach 
@endsection


Explications : on parcourt la collection $businesses passée par le contrôleur. Pour chaque commerce, on affiche le nom, un extrait de description et quelques infos comme le nombre d’avis et une note moyenne (calculée simplement avec avg('rating') sur la collection, pour l’exemple). On propose un lien pour voir la fiche détaillée.En haut, on utilise @auth pour afficher un bouton « Ajouter mon commerce » si l’utilisateur est connecté et n’a pas déjà un commerce (Auth::user()->business null). On évite ainsi de proposer à un commerçant de créer un deuxième commerce dans notre modèle simple (on part du principe d’un commerce par user; adaptez selon vos besoins).Vue fiche d’un commerce (show)Créez resources/views/businesses/show.blade.php. Cette page affiche les détails d’un commerce et la liste de ses avis, plus un formulaire pour en ajouter un nouveau :

@extends('layouts.app')
@section('content')
      {{ $business->name }} 
    {{ $business->description }} 
    @if(session('success')) 
       
        {{ session('success') }} 
       
    @endif 
    Avis ({{ $business->reviews->count() }})    @forelse($business->reviews as $rev) 
       
                  Note : {{ $rev->rating }}/5 
         
        {{ $rev->comment }} 
                  par {{ $rev->author->name }}, le {{ $rev->created_at->format('d/m/Y') }}
         
       
    @empty 
      Aucun avis pour le moment. 
    @endforelse 
    @auth 
      @if(Auth::id() !== $business->user_id) 
         
          Laisser un avis : 
           
            @csrf 
             
              Note (1 à 5) 
               
              @error('rating') 
                {{ $message }} 
              @enderror 
             
             
              Commentaire 
              {{ old('comment') }} 
              @error('comment') 
                {{ $message }} 
              @enderror 
             
            Envoyer           
         
        @else 
                      Vous êtes le propriétaire de ce commerce.           
          Modifier les informations du commerce 
        @endif 
        @else 
          Vous devez 
            vous connecter pour laisser un avis.
           
    @endauth 
@endsection

Vue de détail d’un commerce (show)

Dans cette vue, on affiche le nom et la description du commerce.

On liste les avis existants (la directive @forelse permet de gérer le cas où il n’y a aucun avis).
Pour chaque avis, on montre :

  • la note

  • le commentaire

  • le nom de l’auteur

  • la date

Ensuite, si l’utilisateur est connecté (@auth), on propose soit :

  • le formulaire d’ajout d’avis (si l’utilisateur n’est pas le propriétaire du commerce, pour éviter qu’on laisse un avis sur son propre commerce)

  • ou bien, s’il est propriétaire (Auth::id() === $business->user_id), on affiche un message indiquant qu’il est le propriétaire et on pourrait lui proposer un lien pour éditer les informations du commerce.
    Le propriétaire n’a pas de formulaire d’avis puisqu’il ne devrait pas noter son propre commerce.

Si l’utilisateur n’est pas connecté (@else du @auth), on l’invite à se connecter pour pouvoir poster un avis.

Le formulaire envoie une requête POST sur la route nommée businesses.reviews.store définie plus tôt, incluant le CSRF token (@csrf).
On fait aussi un peu de validation front (champs requis, min/max pour la note) en plus de la validation serveur déjà gérée dans le contrôleur.

Vues de formulaire de création/édition

Pour compléter, créez :

  • resources/views/businesses/create.blade.php

  • resources/views/businesses/edit.blade.php

Ces deux vues seront très similaires : on peut d’ailleurs factoriser en une seule vue Blade en utilisant une variable pour savoir si l’on est en mode édition ou création.

Pour la concision, voici un exemple pour create :

@extends('layouts.app')
@section('content') 
   
    Ajouter mon commerce 
     
      @csrf 
       
        Nom du commerce 
         
        @error('name') 
          {{ $message }} 
        @enderror 
       
       
        Description 
                  {{ old('description') }}
         
        @error('description') 
          {{ $message }} 
        @enderror 
       
      Créer 
     
@endsection  

Vue d’édition edit.blade.php

La vue edit.blade.php aura la même structure que create.blade.php, en préremplissant les valeurs et en pointant vers la bonne route.

Les champs seront préremplis, par exemple :

<input type="text" name="name" id="name" value="{{ old('name', $business->name) }}">
<textarea name="description" id="description">{{ old('description', $business->description) }}</textarea>

Le formulaire pointera vers la route de mise à jour, avec spoofing de méthode PUT :

<form action="{{ route('businesses.update', $business->id) }}" method="POST">
    @csrf
    @method('PUT')

    {{-- champs identiques à create.blade.php --}}

    <button type="submit">Enregistrer les modifications</button>
</form>

N’oubliez pas d’afficher les messages de succès/erreur flash (comme dans show.blade.php) afin d’informer l’utilisateur après une redirection suivant une action (mise à jour réussie, erreur de validation, etc.).

Breeze fournit déjà un menu dans layouts/navigation.blade.php.
Vous pouvez y ajouter un lien vers la liste des commerces (page d’accueil publique), par exemple un lien vers /businesses, afin que l’utilisateur puisse y accéder facilement depuis le dashboard.

Vous pouvez aussi enrichir l’interface avec des liens comme :

  • « Mes Avis »

  • « Mon Commerce » (si l’utilisateur possède un commerce)

Cela dépasse légèrement le cadre du tutoriel, mais il est important de garder en tête l’expérience utilisateur globale.

À ce stade, l’interface est prête.
Grâce à Tailwind (utilisé par Breeze), les classes CSS comme container, mx-auto, text-xl, etc., permettent d’avoir une mise en page propre sans écrire de CSS personnalisé.

Vous pouvez ensuite personnaliser le style, ajouter des étoiles pour les notes, et affiner la présentation selon vos besoins.

Étape 7 : Tester l’application SaaS localement

Il est temps de tester notre mini-SaaS en situation réelle sur votre serveur de développement (Laravel Serve ou autre). Voici quelques scénarios à essayer.

Créer un compte commerçant et ajouter un commerce

Allez sur /register pour créer un nouvel utilisateur (supposé être un commerçant).
Après inscription, cliquez sur « Ajouter mon commerce ».

Remplissez le formulaire avec le nom du commerce et une description, puis validez. Vous devriez être redirigé vers la page de votre commerce avec un message de succès.

Vérifiez que vous voyez bien le lien « Modifier les informations du commerce » et que personne d’autre n’est listé comme propriétaire.

En base de données, la table businesses doit contenir ce commerce avec le user_id correspondant à votre compte.

Lister les commerces

Déconnectez-vous (ou utilisez un autre navigateur) et allez sur /businesses.
Vous devriez voir la liste incluant le commerce créé.

Si plusieurs commerces existent (créez-en d’autres si besoin), vérifiez que la liste s’affiche correctement avec le nombre d’avis (0 pour le moment) et les descriptions tronquées.

Poster des avis en tant que client

Créez un autre compte utilisateur (via /register).
Allez sur la page d’un commerce et remplissez le formulaire « Laisser un avis ».
Mettez une note et un commentaire, puis soumettez.

L’avis doit apparaître en dessous, avec votre nom d’utilisateur et la date.

Essayez d’en poster un deuxième. Ensuite, connectez-vous avec un troisième compte pour poster un autre avis et voir plusieurs avis listés.

Vérifier l’isolation des données

Connectez-vous en tant que propriétaire du commerce et assurez-vous que vous ne voyez pas d’actions autorisées uniquement pour les clients ou d’autres propriétaires.

Vérifiez également que si vous tentez d’accéder à l’URL d’édition d’un commerce qui ne vous appartient pas, vous obtenez bien une erreur 403.

Par exemple, si le commerce avec l’ID 2 appartient à un autre utilisateur, accéder à /businesses/2/edit doit renvoyer un 403.

Tester la suppression (si implémentée)

Si la méthode destroy est implémentée, essayez de supprimer un commerce. Cela doit supprimer le commerce et tous ses avis, puis vous rediriger correctement.

Vérifiez qu’un utilisateur non propriétaire ne puisse pas appeler cette action.

Conclusion et pistes d’amélioration

Vous avez créé un SaaS Laravel 12 multi-utilisateurs sur une architecture à base de données unique.

Ce tutoriel vous a permis de découvrir la structure multi-locataire, les modèles Eloquent, les relations, les protections d’accès, les vues Blade et Laravel Breeze.

Résumé des points clés

  • Laravel simplifie la création d’applications SaaS

  • Chaque donnée doit être liée à un tenant

  • Ici, chaque business représente un tenant

  • L’utilisation de business_id permet l’isolation

  • On peut renforcer encore la sécurité via Policies, scopes, packages, etc.

  • Breeze facilite l’authentification

Idées d’améliorations

  • Ajouter un système de rôles (admin, manager…)

  • Implémenter des notifications

  • Améliorer l’affichage (notation, cartes…)

  • Permettre aux commerçants de répondre aux avis

  • Autoriser plusieurs commerces par utilisateur

  • Améliorer l’évolutivité (schémas multiples ou bases séparées)

Laravel 12 permet de bâtir rapidement un SaaS multi-locataire fonctionnel. Vous disposez maintenant d’une base solide que vous pouvez adapter et faire évoluer selon vos besoins.

Bonne continuation dans vos projets Laravel SaaS.

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !