Laravel 13 tutorial #3: MVC, controllers and Blade views
verified on 7 September 2026 · 6 min
MVC separates the data (model), the display (view) and the logic (controller). Create a controller with php artisan make:controller, return a view with view('nom', [...]), and print values in Blade with {{ $variable }}, which escapes HTML automatically.
MVC splits an application into three responsibilities: the model describes the data, the view displays it, and the controller connects the two. This chapter builds a controller and a few Blade views. Models come in chapter 6, once there is a database to query.
Controllers
A controller receives a request, gathers what the page needs, and returns a view. Create one:
php artisan make:controller IndexControllerINFO Controller [app/Http/Controllers/IndexController.php] created successfully.The generated file is almost empty. Laravel 13 imports Request by default, even though the class is not used yet:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class IndexController extends Controller
{
//
}App\Http\Controllers is written with backslashes, not forward slashes, and never run together as a single word. A line such as namespace AppHttpControllers; does not name the right namespace, and Composer’s autoloader will never find the class. If you copy code from a site that has swallowed the backslashes, that is the first thing to check.
Add a method that returns some text:
<?php
namespace App\Http\Controllers;
class IndexController extends Controller
{
public function index(): string
{
return 'Ceci vient de IndexController';
}
}Then point the home route at it:
<?php
use App\Http\Controllers\IndexController;
use Illuminate\Support\Facades\Route;
Route::get('/', [IndexController::class, 'index']);Reload http://127.0.0.1:8000: the sentence shows up. The route no longer holds any logic, it only says where to find it.
Returning a view instead of a string
use Illuminate\View\View;
public function index(): View
{
return view('welcome');
}The view() helper takes the file name without its extension: welcome maps to resources/views/welcome.blade.php. A dot acts as a folder separator, so view('partials.sidebar') loads resources/views/partials/sidebar.blade.php.
Single-action controllers
When a controller has only one thing to do, the __invoke method saves naming it. The file already exists since the start of the chapter: --force overwrites it.
php artisan make:controller IndexController --invokable --forceThe generated file contains an empty __invoke(Request $request) method preceded by a comment. Replace it with this one:
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
class IndexController extends Controller
{
public function __invoke(): View
{
return view('welcome');
}
}The route gets shorter too: there is no method left to point at.
Route::get('/', IndexController::class);This tutorial uses that form for the home page, the categories and the tags, and the classic form for posts, which will need two methods.
Passing data to the view
The second argument to view() is an array. Each key becomes a variable in the template:
public function __invoke(): View
{
return view('accueil', [
'nom' => 'Damien',
'articles' => ['Premier article', 'Deuxième article'],
]);
}In the view, $nom and $articles are available.
Blade views
Blade is Laravel’s template engine. A .blade.php file is HTML with a handful of directives added on top. It is compiled to PHP once, then cached.
Printing a value
<p>Bonjour, {{ $nom }}.</p>Double braces escape the content automatically: if $nom contains <script>, the browser prints the text instead of running it. That is the default protection against code injection.
When you deliberately want to output HTML, the body of a post written in the admin panel, for instance, use the unescaped form:
<div>{!! $post->content !!}</div>Only use it on values whose origin you control. On visitor input, it is a security hole.
Conditions and loops
@if (count($articles) === 1)
Un seul article.
@elseif (count($articles) > 1)
{{ count($articles) }} articles.
@else
Aucun article.
@endifThe @foreach loop has a useful variant, @forelse, which handles the empty list without an extra condition:
@forelse ($articles as $article)
<h2>{{ $article }}</h2>
@empty
<p>Aucun article pour le moment.</p>
@endforelseLoops
@foreach is the most common one, but Blade covers every PHP loop:
@for ($i = 1; $i <= 3; $i++)
Passage numéro {{ $i }}
@endfor
@while ($compteur > 0)
Il reste {{ $compteur-- }}
@endwhileInside a @foreach, Blade exposes a $loop variable that saves you counting by hand:
@foreach ($articles as $article)
{{ $loop->iteration }} sur {{ $loop->count }} :
{{ $article }}
@if ($loop->first) (le premier) @endif
@if ($loop->last) (le dernier) @endif
@endforeachOn a list of two posts, this template produces:
1 sur 2 : A (le premier)
2 sur 2 : B (le dernier)$loop also carries index (starting at zero), remaining, even, odd and parent to reach the enclosing loop.
The switch
@switch($niveau)
@case('debutant')
Pour débuter
@break
@case('avance')
Pour aller plus loin
@break
@default
Niveau non précisé
@endswitchThe @break is required at the end of every @case, exactly as in PHP: without it, execution falls through to the next case.
Template inheritance
The main benefit of Blade is writing the skeleton of the site only once. Create resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title', config('app.name'))</title>
</head>
<body>
<header>
<a href="https://www.gekkode.com/en/">{{ config('app.name') }}</a>
</header>
<main>
@yield('content')
</main>
<footer>
<p>{{ config('app.name') }}</p>
</footer>
</body>
</html>@yield('content') reserves a slot. The second argument to @yield is the value used when the section is not defined.
A real page extends that layout and fills the slots:
@extends('layouts.app')
@section('title', 'Accueil')
@section('content')
<h1>Bonjour, {{ $nom }}.</h1>
@forelse ($articles as $article)
<h2>{{ $article }}</h2>
@empty
<p>Aucun article pour le moment.</p>
@endforelse
@endsectionA short section fits on one line with @section('title', 'Accueil'). For a block, open with @section and close with @endsection.
Including a reusable chunk
@include drops another view in wherever you need it, with access to the same variables:
@include('partials.sidebar')This tutorial uses it for the sidebar and the post list, reused on five pages.
Components or inheritance?
Laravel offers a second approach, Blade components, with a tag-based syntax:
<x-layout title="Accueil">
<h1>Bonjour</h1>
</x-layout>Both mechanisms coexist in Laravel 13 and neither is deprecated. Components pay off when the building blocks take parameters and nest into one another, @extends stays more direct for a plain page skeleton. This tutorial sticks to @extends, which is easier to follow to start with. The Blade documentation covers components in detail.
A note on the view cache
Blade compiles every view to PHP under storage/framework/views. In development, the file is recompiled automatically as soon as it changes. If an edit does not show up, clear the cache:
php artisan view:clearThe next chapter installs the admin panel you will use to write the posts.