Week 9: Laravel Setup, Routing & Blade

Modules 1 built raw PHP + MySQL from scratch — useful for understanding what a framework actually automates. From here on, that framework is Laravel: the most widely used PHP framework in production, and the one this course builds APIs and applications on through Week 16. This week is the on-ramp — installation, routing, and Blade templates.

Module 9 of 28 Week 9 of 32 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Install Laravel & navigate its project structure and artisan CLI
  • Define routes, route parameters & use route model binding
  • Build views with Blade templates, layouts & components

1. Installing Laravel

Laravel is installed through Composer, the same tool from Module 1 — it's ultimately a large, well-organized set of PHP packages, not a separate binary:

terminal
composer create-project laravel/laravel task-tracker
cd task-tracker

php artisan serve
# Server running on [http://127.0.0.1:8000]

php artisan serve is Laravel's own development server — the same idea as php -S localhost:8000 from Week 1, but pre-configured for Laravel's directory structure. Visit http://127.0.0.1:8000 and you'll see Laravel's default welcome page.

Laravel also needs a database connection — configured through a .env file rather than hardcoded, addressing the credentials problem from Week 7 directly:

.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=task_tracker
DB_USERNAME=root
DB_PASSWORD=your_password
.env is never committed

Laravel ships with a .env.example template (safe to commit, no real secrets) and a .gitignore that already excludes .env itself. Every developer and deploy environment gets its own .env with real credentials — exactly the "read config from environment, not hardcoded" pattern flagged back in Week 7, now built into the framework's defaults.

2. Project Structure & artisan

The directories you'll touch constantly, from day one:

  • app/Models/ — Eloquent models (Week 11)
  • app/Http/Controllers/ — request-handling classes (Week 10)
  • routes/web.php & routes/api.php — route definitions
  • resources/views/ — Blade templates
  • database/migrations/ — versioned schema changes (Week 11)
  • .env — environment-specific configuration

artisan is Laravel's command-line tool — it generates boilerplate, runs migrations, and inspects your app:

terminal
php artisan list                    # every available command
php artisan make:controller TaskController
php artisan make:model Task -m      # -m also generates a migration
php artisan route:list              # every registered route, at a glance
php artisan tinker                  # an interactive REPL with your app already loaded

3. Routing

A route maps an HTTP verb + URI pattern to the code that handles it. For now, routes point straight at a closure; Week 10 moves this logic into controllers:

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/tasks', function () {
    return "List of tasks would go here";
});

// Route parameters
Route::get('/tasks/{id}', function (int $id) {
    return "Showing task #{$id}";
})->where('id', '[0-9]+'); // constrain the parameter to digits only

// Named routes -- referenced by name, not hardcoded URL, elsewhere in the app
Route::get('/tasks/{id}/edit', function (int $id) {
    return "Editing task #{$id}";
})->name('tasks.edit');

Naming routes matters immediately: route('tasks.edit', ['id' => 5]) generates the correct URL from anywhere in the app (a Blade template, a redirect), so renaming the URL pattern later only requires touching the route definition, not every place that links to it.

4. Route Model Binding

Once a Task Eloquent model exists (Week 11 covers models properly — this previews the payoff), Laravel can resolve a route parameter directly into a model instance, skipping the manual "look it up by ID" step entirely:

routes/web.php — with route model binding
<?php

use App\Models\Task;
use Illuminate\Support\Facades\Route;

Route::get('/tasks/{task}', function (Task $task) {
    // $task is already the fetched model -- Laravel ran
    // Task::findOrFail($id) for you, and 404s automatically if not found
    return $task->title;
});

The {task} parameter name matches the Task $task type hint, which is how Laravel knows which model to query and by which column (the primary key, by default). This single feature quietly eliminates a huge amount of repetitive "fetch or 404" boilerplate you'd otherwise write by hand.

5. Blade Templates

Blade is Laravel's templating engine — .blade.php files mixing HTML with a compact directive syntax, compiled to plain PHP and cached automatically:

resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>
<head>
  <title>@yield('title', 'Task Tracker')</title>
</head>
<body>
  <nav>...</nav>
  <main>
    @yield('content')
  </main>
</body>
</html>
resources/views/tasks/index.blade.php
@extends('layouts.app')

@section('title', 'My Tasks')

@section('content')
  <h1>Tasks</h1>
  @if ($tasks->isEmpty())
    <p>No tasks yet.</p>
  @else
    <ul>
      @foreach ($tasks as $task)
        <li>
          {{ $task->title }}
          @if ($task->is_done) (done) @endif
        </li>
      @endforeach
    </ul>
  @endif
@endsection

{{ $task->title }} auto-escapes its output — Blade calls htmlspecialchars() for you on every {{ }} expression, so the XSS discipline from Week 8 is the framework's default, not something you opt into manually. {!! $raw !!} prints unescaped and should be used rarely, deliberately, and never on user-supplied content.

A Blade component extracts a reusable piece of markup into its own file, callable with an HTML-like tag:

resources/views/components/task-badge.blade.php
<span class="badge {{ $done ? 'badge-done' : 'badge-pending' }}">
  {{ $done ? 'Done' : 'Pending' }}
</span>
usage, inside another Blade view
<x-task-badge :done="$task->is_done" />

6. Hands-on Exercise

Hands-on

Scaffold the task-tracker app's routes and views

Get a fresh Laravel install running and build its route + view skeleton, before real data joins in Week 10.

Requirements:

  1. composer create-project laravel/laravel task-tracker, confirm php artisan serve works, and connect a local MySQL database via .env.
  2. Build a Blade layout (layouts/app.blade.php) with a shared header/nav and a @yield('content') section.
  3. Define named routes: tasks.index (GET /tasks), tasks.show (GET /tasks/{id}), tasks.create (GET /tasks/create) — each returning a view for now, with hardcoded sample data (a real array, not a database yet).
  4. Build a Blade component <x-task-badge :done="..."/> and use it in the tasks index view's list.
  5. Add links between the pages using route('tasks.show', ['id' => $task['id']]) rather than hardcoded URLs.
Hint

Run php artisan route:list at any point to see exactly which routes are registered and confirm your names and URIs match what you intended.

7. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

Why name a route (->name('tasks.edit')) instead of just hardcoding its URL everywhere it's linked?

A named route can be referenced by name (route('tasks.edit', [...])) from any Blade view, controller or redirect, and Laravel generates the correct URL from the current route definition. If the URL pattern ever changes, only the route definition needs updating — every place that links to it via the name keeps working automatically, instead of needing a find-and-replace across the codebase.

Q2

What does route model binding actually save you from writing?

The manual "fetch this model by the ID in the URL, or return a 404 if it doesn't exist" logic that would otherwise appear at the start of nearly every route handler. Typing the closure/controller parameter as the model class (Task $task, matching a {task} route parameter) tells Laravel to run that lookup — including the automatic 404 — before your code even runs.

Q3

Why does {{ $task->title }} in Blade not need an explicit htmlspecialchars() call?

Blade compiles every {{ ... }} expression into PHP that calls htmlspecialchars() (technically Laravel's e() helper) around the value automatically. The XSS-prevention discipline from Week 8 is baked in as the framework's default behavior — you'd have to deliberately opt out with {!! ... !!} to print something unescaped.

Q4

Why does Laravel read database credentials from .env instead of a PHP config file with the values written directly into it?

.env is excluded from version control by default, so real credentials never end up committed to the repository, while .env.example documents which variables are needed without exposing real values. Each environment (your machine, a teammate's, staging, production) supplies its own .env with its own credentials — the same "read config from environment, not hardcoded" principle flagged back in Week 7, now enforced by the framework's own conventions.