# Laravel 11: Een complete applicatie bouwen vanaf nul > Uitgebreide handleiding voor Laravel 11: installatie, Eloquent-modellen, authenticatie met Breeze, controllers, routes, autorisatie met policies, REST API met Sanctum, testen met Pest en productie-deployment. - Published: 2026-01-14 - Updated: 2026-04-08 - Author: SharpSkill - Tags: laravel, php, rest api, eloquent, tutorial - Reading time: 15 min --- Laravel 11 introduceert een gestroomlijnde projectstructuur, native health checks en een vernieuwde configuratie op basis van `bootstrap/app.php`. Deze handleiding doorloopt het volledige traject: van installatie tot productie-deployment van een taakbeheer-applicatie. > **Nieuw in Laravel 11** > > Laravel 11 verwijdert talloze boilerplate-bestanden (Http Kernel, Console Kernel, Middleware-directory) en introduceert een gecentraliseerde configuratie in `bootstrap/app.php`. Health-check routes, geleidelijke encryptiesleutel-rotatie en een vernieuwd starter kit-ecosysteem behoren tot de belangrijkste wijzigingen. ## Installatie en projectconfiguratie De Laravel-installer genereert het projectskelet in enkele seconden. Vereisten: PHP 8.2+ en Composer. ```bash # Install Laravel installer globally composer global require laravel/installer # Create a new Laravel 11 project laravel new taskmanager # Navigate to the project cd taskmanager ``` Het `.env`-bestand configureert de databaseverbinding. Laravel 11 gebruikt standaard SQLite; voor productieomgevingen verdient PostgreSQL of MySQL de voorkeur. ```env # .env DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=taskmanager DB_USERNAME=postgres DB_PASSWORD=secret ``` Na de configuratie worden de initiele migraties uitgevoerd en de ontwikkelserver gestart. ```bash # Run initial migrations php artisan migrate # Start the development server php artisan serve ``` De applicatie is nu bereikbaar op `http://localhost:8000`. ## Modellen en migraties aanmaken Eloquent-modellen vormen de kern van elke Laravel-applicatie. Het volgende commando maakt gelijktijdig een model, migratie, factory en seeder aan. ```bash # Create model with migration, factory, and seeder php artisan make:model Task -mfs ``` De migratie definieert de tabelstructuur. Belangrijk: `softDeletes()` maakt logisch verwijderen mogelijk zonder gegevensverlies. ```php // database/migrations/xxxx_create_tasks_table.php public function up(): void { Schema::create('tasks', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('title'); $table->text('description')->nullable(); $table->enum('status', ['pending', 'in_progress', 'completed'])->default('pending'); $table->enum('priority', ['low', 'medium', 'high'])->default('medium'); $table->date('due_date')->nullable(); $table->timestamps(); $table->softDeletes(); }); } ``` Het Eloquent-model declareert de fillable velden, casts en de relatie met User. ```php // app/Models/Task.php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Task extends Model { use HasFactory, SoftDeletes; protected $fillable = [ 'title', 'description', 'status', 'priority', 'due_date', 'user_id', ]; protected $casts = [ 'due_date' => 'date', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } } ``` > **Soft Deletes in de praktijk** > > Soft Deletes markeren records als verwijderd zonder ze fysiek uit de database te verwijderen. Dit is bijzonder nuttig voor audit trails en gegevensherstel. Met `Task::withTrashed()` kunnen ook verwijderde records worden opgevraagd. ## Authenticatie instellen met Breeze Laravel Breeze biedt een lichtgewicht authenticatieoplossing met login, registratie, wachtwoord-reset en e-mailverificatie. ```bash # Install Breeze composer require laravel/breeze --dev # Scaffold with Blade stack php artisan breeze:install blade # Install frontend dependencies and build npm install && npm run build # Run migrations for auth tables php artisan migrate ``` Het User-model wordt uitgebreid met de `hasMany`-relatie naar Tasks. ```php // app/Models/User.php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use HasFactory, Notifiable; protected $fillable = [ 'name', 'email', 'password', ]; protected $hidden = [ 'password', 'remember_token', ]; protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } public function tasks(): HasMany { return $this->hasMany(Task::class); } } ``` ## Controllers en routes aanmaken Een resourceful controller implementeert alle CRUD-operaties. Laravel 11 maakt gebruik van de vereenvoudigde route-declaratie in `routes/web.php`. ```php // app/Http/Controllers/TaskController.php namespace App\Http\Controllers; use App\Models\Task; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; class TaskController extends Controller { public function index(): View { $tasks = Auth::user()->tasks() ->orderBy('due_date') ->paginate(10); return view('tasks.index', compact('tasks')); } public function create(): View { return view('tasks.create'); } public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'status' => 'in:pending,in_progress,completed', 'priority' => 'in:low,medium,high', 'due_date' => 'nullable|date|after:today', ]); Auth::user()->tasks()->create($validated); return redirect()->route('tasks.index') ->with('success', 'Task created successfully.'); } public function edit(Task $task): View { $this->authorize('update', $task); return view('tasks.edit', compact('task')); } public function update(Request $request, Task $task): RedirectResponse { $this->authorize('update', $task); $validated = $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'status' => 'in:pending,in_progress,completed', 'priority' => 'in:low,medium,high', 'due_date' => 'nullable|date', ]); $task->update($validated); return redirect()->route('tasks.index') ->with('success', 'Task updated successfully.'); } public function destroy(Task $task): RedirectResponse { $this->authorize('delete', $task); $task->delete(); return redirect()->route('tasks.index') ->with('success', 'Task deleted successfully.'); } } ``` De routes worden geregistreerd als resource route, beschermd door de `auth`-middleware. ```php // routes/web.php use App\Http\Controllers\TaskController; Route::middleware(['auth', 'verified'])->group(function () { Route::resource('tasks', TaskController::class) ->except(['show']); }); ``` ## Autorisatie met policies instellen Policies bundelen de autorisatielogica en zorgen ervoor dat gebruikers alleen hun eigen taken kunnen bewerken. ```bash # Generate a policy for the Task model php artisan make:policy TaskPolicy --model=Task ``` De policy controleert of de geauthenticeerde gebruiker de eigenaar van de taak is. ```php // app/Policies/TaskPolicy.php namespace App\Policies; use App\Models\Task; use App\Models\User; class TaskPolicy { public function update(User $user, Task $task): bool { return $user->id === $task->user_id; } public function delete(User $user, Task $task): bool { return $user->id === $task->user_id; } } ``` > **Beveiliging bij policies** > > Policies moeten worden aangeroepen in elke controller-methode die gegevens wijzigt. Zonder autorisatiecontrole kunnen gebruikers via URL-manipulatie records van anderen bewerken of verwijderen. De methode `$this->authorize()` gooit automatisch een 403-exceptie wanneer de controle faalt. ## Een REST API bouwen met Sanctum Laravel Sanctum biedt lichtgewicht authenticatie voor SPA's en mobiele apps. Vanaf Laravel 11 is de API-configuratie met een enkel commando geregeld. ```bash # Install API support (includes Sanctum) php artisan install:api ``` De API-controller retourneert JSON-responses via API Resources, die de datastructuur bepalen. ```php // app/Http/Controllers/Api/TaskController.php namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Resources\TaskResource; use App\Models\Task; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; class TaskController extends Controller { public function index(Request $request): AnonymousResourceCollection { $tasks = $request->user()->tasks() ->orderBy('created_at', 'desc') ->paginate(15); return TaskResource::collection($tasks); } public function store(Request $request): TaskResource { $validated = $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'status' => 'in:pending,in_progress,completed', 'priority' => 'in:low,medium,high', 'due_date' => 'nullable|date|after:today', ]); $task = $request->user()->tasks()->create($validated); return new TaskResource($task); } public function update(Request $request, Task $task): TaskResource { $this->authorize('update', $task); $validated = $request->validate([ 'title' => 'sometimes|required|string|max:255', 'description' => 'nullable|string', 'status' => 'in:pending,in_progress,completed', 'priority' => 'in:low,medium,high', 'due_date' => 'nullable|date', ]); $task->update($validated); return new TaskResource($task); } public function destroy(Task $task) { $this->authorize('delete', $task); $task->delete(); return response()->json(['message' => 'Task deleted'], 200); } } ``` De API Resource bepaalt welke velden in de JSON-response worden opgenomen. ```php // app/Http/Resources/TaskResource.php namespace App\Http\Resources; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; class TaskResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'description' => $this->description, 'status' => $this->status, 'priority' => $this->priority, 'due_date' => $this->due_date?->format('Y-m-d'), 'created_at' => $this->created_at->toISOString(), 'updated_at' => $this->updated_at->toISOString(), ]; } } ``` De API-routes worden beschermd door Sanctums `auth:sanctum`-middleware. ```php // routes/api.php use App\Http\Controllers\Api\TaskController; Route::middleware('auth:sanctum')->group(function () { Route::apiResource('tasks', TaskController::class); }); ``` ## Geautomatiseerd testen met Pest Pest biedt een elegante syntax voor PHP-tests. De volgende tests dekken de CRUD-operaties en autorisatie. ```php // tests/Feature/TaskTest.php use App\Models\Task; use App\Models\User; beforeEach(function () { $this->user = User::factory()->create(); }); test('user can create a task', function () { $response = $this->actingAs($this->user)->post('/tasks', [ 'title' => 'Test Task', 'description' => 'Test description', 'priority' => 'high', ]); $response->assertRedirect('/tasks'); $this->assertDatabaseHas('tasks', [ 'title' => 'Test Task', 'user_id' => $this->user->id, ]); }); test('user can update own task', function () { $task = Task::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->put("/tasks/{$task->id}", [ 'title' => 'Updated Task', 'status' => 'completed', 'priority' => 'low', ]); $response->assertRedirect('/tasks'); expect($task->fresh()->title)->toBe('Updated Task'); }); test('user cannot update another users task', function () { $otherUser = User::factory()->create(); $task = Task::factory()->create(['user_id' => $otherUser->id]); $response = $this->actingAs($this->user)->put("/tasks/{$task->id}", [ 'title' => 'Hacked Task', ]); $response->assertForbidden(); }); test('user can delete own task', function () { $task = Task::factory()->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->delete("/tasks/{$task->id}"); $response->assertRedirect('/tasks'); $this->assertSoftDeleted('tasks', ['id' => $task->id]); }); test('api returns paginated tasks', function () { Task::factory(20)->create(['user_id' => $this->user->id]); $response = $this->actingAs($this->user)->getJson('/api/tasks'); $response->assertOk() ->assertJsonCount(15, 'data') ->assertJsonStructure([ 'data' => [['id', 'title', 'status', 'priority']], 'meta' => ['current_page', 'last_page'], ]); }); ``` Het uitvoeren van de tests gebeurt met een enkel commando. ```bash # Run all tests php artisan test # Run with coverage php artisan test --coverage ``` ## Productie-optimalisaties Voor deployment verbeteren de volgende cache-commando's de prestaties aanzienlijk. ```bash # Cache configuration, routes, and views php artisan config:cache php artisan route:cache php artisan view:cache # Optimize autoloader composer install --optimize-autoloader --no-dev ``` Het `.env.production`-bestand bevat productiespecifieke instellingen. ```env # .env.production APP_ENV=production APP_DEBUG=false APP_URL=https://taskmanager.example.com LOG_CHANNEL=stack LOG_LEVEL=error CACHE_STORE=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis ``` > **Gevoelige omgevingsvariabelen** > > Het `.env`-bestand mag nooit in versiebeheer worden opgenomen. `APP_KEY`, databasewachtwoorden en API-sleutels dienen via beveiligde omgevingsvariabelen van de hosting provider te worden geconfigureerd. Het commando `php artisan env:encrypt` versleutelt het bestand voor veilige deployments. ## Conclusie Deze handleiding heeft de essentiiele bouwstenen van een Laravel 11-applicatie behandeld: - **Projectconfiguratie**: Laravel installer, omgevingsconfiguratie en initiele migraties - **Eloquent-modellen**: relaties, casts, soft deletes en factories voor gestructureerd gegevensbeheer - **Authenticatie**: Breeze levert login, registratie en wachtwoord-reset in enkele minuten - **Controllers en routes**: resourceful controllers en middleware-beschermde routegroepen - **Autorisatie**: policies garanderen dat gebruikers alleen hun eigen resources beheren - **REST API**: Sanctum-tokens en API Resources voor mobiele en SPA-clients - **Tests**: Pest-tests dekken CRUD-operaties en autorisatieregels - **Productie**: caching, autoloader-optimalisatie en beveiligde omgevingsconfiguratie --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/laravel/laravel-11-building-complete-application