Getting Started with Laravel

Reviewed & published by Brayan K

Laravel is the most popular PHP framework in the world. In this lesson you'll install it with Composer, understand its MVC structure, declare routes, write controllers, render Blade templates, model your data with Eloquent, and drive it all from the Artisan command line.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Installing Laravel with Composer

Laravel is distributed as a Composer package, so creating a new app is a single command. composer create-project laravel/laravel blog downloads the framework, installs all its dependencies, and scaffolds a complete project. From there, php artisan serve launches a local development server. Artisan — Laravel's command-line tool — is how you'll generate code, run migrations, and manage the app throughout the lesson.

# Laravel ships as a Composer package, so you install it with Composer.
# This one command downloads the framework and scaffolds a whole app.

composer create-project laravel/laravel blog   # create a project named "blog"

cd blog                 # move into the new project directory
php artisan serve       # start the built-in dev server on http://127.0.0.1:8000

# "artisan" is Laravel's command-line tool. You'll use it constantly:
#   php artisan list        -> show every available command
#   php artisan make:model  -> generate code (models, controllers, migrations...)
#   php artisan migrate     -> run your database migrations

After scaffolding, the project follows a fixed layout: routes live in routes/web.php, controllers in app/Http/Controllers, models in app/Models, and views in resources/views. Knowing where each kind of file belongs is half of learning a framework.

2️⃣ Routing: Mapping URLs to Actions

A route connects an HTTP method and URL to the code that should run. You declare them in routes/web.php with calls like Route::get, Route::post, and friends. An action can be an inline closure or — better for real logic — a controller method. Routes can take URL parameters like and be wrapped in middleware groups so, for example, every route in a group requires a logged-in user.

<?php
// routes/web.php — every URL your app answers is declared here.
// Each route maps an HTTP method + URL to an action (a closure or controller).

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;

// A simple closure route: GET / returns a Blade view called "welcome"
Route::get('/', function () {
    return view('welcome');               // renders resources/views/welcome.blade.php
});

// A route with a URL parameter ({id}) handled by a controller method
Route::get('/posts/{id}', [PostController::class, 'show']);

// Group of routes that all require a logged-in user (auth middleware)
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', [PostController::class, 'index']);
    Route::post('/posts', [PostController::class, 'store']);   // create a post
});
?>

Run php artisan route:list any time to see every route your app answers and which action handles it — invaluable when a project grows large.

3️⃣ Controllers: The Logic Layer

A controller groups the actions for one resource — listing, showing, creating, updating. Generate one with php artisan make:controller PostController. Each public method handles a route: it fetches data (usually via Eloquent), maybe validates input, and returns a view or a redirect. Crucially, a controller should never trust raw input — call $request->validate() first so the rest of the method only runs on clean data.

<?php
// app/Http/Controllers/PostController.php
// A controller groups the logic for one resource. Generate it with:
//   php artisan make:controller PostController

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    // Show a list of posts. The view receives the data via compact().
    public function index()
    {
        $posts = Post::latest()->get();        // Eloquent: all posts, newest first
        return view('posts.index', compact('posts'));
    }

    // Show one post by id. findOrFail() auto-throws a 404 if it's missing.
    public function show(int $id)
    {
        $post = Post::findOrFail($id);
        return view('posts.show', compact('post'));
    }

    // Create a post from form input — but validate it FIRST.
    public function store(Request $request)
    {
        $data = $request->validate([
            'title' => 'required|max:255',     // must be present, max 255 chars
            'body'  => 'required',
        ]);
        Post::create($data);                   // mass-assign the validated fields
        return redirect('/dashboard');
    }
}
?>

Notice findOrFail(): it returns the record or automatically throws a 404 if the id doesn't exist, so you don't have to write that check by hand. This kind of sensible default is everywhere in Laravel.

4️⃣ Blade Templates: The View Layer

Blade is Laravel's templating engine. Files end in .blade.php and compile down to plain PHP. Blade adds clean directives — @if, @foreach, @extends for layout inheritance — and the syntax that echoes a value and escapes it, stopping XSS automatically. That makes views readable while keeping output safe by default.

{{-- resources/views/posts/index.blade.php --}}
{{-- Blade is Laravel's template engine. Files end in .blade.php. --}}

@extends('layouts.app')          {{-- inherit a shared parent layout --}}

@section('content')              {{-- fill the parent's "content" slot --}}
    <h1>Latest Posts</h1>

    @forelse ($posts as $post)    {{-- loop, with a built-in "empty" case --}}
        <article>
            {{-- {{ }} echoes AND escapes output, stopping XSS automatically --}}
            <h2>{{ $post->title }}</h2>
            <p>{{ $post->body }}</p>
        </article>
    @empty
        <p>No posts yet.</p>
    @endforelse
@endsection

The @extends / @section pair lets every page share one parent layout (header, nav, footer) and only fill in its own content — the templating equivalent of inheritance.

5️⃣ Eloquent ORM: The Model Layer

Eloquent is Laravel's object-relational mapper. Each model class maps to a database table, and table rows become objects you can read, change, and save — no hand-written SQL. You define the table's structure in a migration (run with php artisan migrate), set $fillable for safe mass-assignment, and declare relationships like belongsTo and hasMany as methods so related records are just a property away.

<?php
// app/Models/Post.php — an Eloquent MODEL maps to a database table.
// Generate the model AND its migration together with:
//   php artisan make:model Post -m

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // Columns that are safe to mass-assign via create()/update()
    protected $fillable = ['title', 'body', 'user_id'];

    // Relationship: a Post belongs to one User (its author)
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// --- Using the model anywhere in your app ---
$post = Post::create(['title' => 'Hi', 'body' => 'Body', 'user_id' => 1]);
$post = Post::find(1);                 // SELECT * FROM posts WHERE id = 1
$author = $post->user->name;           // follows the belongsTo relationship
$recent = Post::where('user_id', 1)->latest()->take(5)->get();
?>

Now you try the two core skills — declaring a route and validating input. Fill in each ___ using the 👉 hint, then run the matching artisan command and check the Output panel.

<?php
// 🎯 YOUR TURN — register a route and point it at a controller method.
// Fill in each blank marked ___ , then run "php artisan route:list".

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;

// 1) Answer GET requests to /about with a closure that returns a view
Route::___('/about', function () {     // 👉 the HTTP verb: get
    return view('about');
});

// 2) Send GET /posts to PostController's index method
Route::get('/posts', [PostController::class, '___']);   // 👉 the method name: index

// ✅ route:list should then show both routes.
?>

One more. A controller must validate before it saves. Add the validation call and the missing rule.

<?php
// 🎯 YOUR TURN — validate form input before saving it.
// A controller must never trust raw input. Add the rules, then save.

public function store(Request $request)
{
    // 1) Require a title (max 255) and a body
    $data = $request->___([            // 👉 the method that validates: validate
        'title' => 'required|max:255',
        'body'  => '___',              // 👉 make body required too: required
    ]);

    // 2) Create the post from the validated data
    Post::create($data);
    return redirect('/posts');
}

// ✅ Submitting an empty title redirects back with an error,
//    and store() never runs the create() line.
?>

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Laravel Basics

Command / SnippetWhat It Does
composer create-project laravel/laravel appScaffold a new Laravel app
php artisan serveStart the local dev server
php artisan make:model Post -mGenerate a model + migration
php artisan make:controller PostControllerGenerate a controller
php artisan migrateRun database migrations
Route::get('/x', [C::class, 'm'])Map a URL to a controller method
$request->validate([...])Validate incoming request input
Model::create([...])Insert a row via Eloquent
$this->hasMany(Post::class)Define a one-to-many relationship
@extends / @section / @forelseBlade layout + loop directives

Mini-Challenge: A Tasks Feature

No code is filled in this time — just a brief and an outline. Build it yourself in a real Laravel project: generate the model and migration, wire up routes, list tasks in a Blade view, and add a validated create action. This is the full MVC loop you'll use in every Laravel app.

<?php
// 🎯 MINI-CHALLENGE: A tiny "tasks" feature, end to end.
// No code is filled in — work from the steps below.
//
// 1. Generate the pieces with artisan:
//      php artisan make:model Task -m            (model + migration)
//      php artisan make:controller TaskController
// 2. In the migration's up(), add columns: $table->string('title');
//                                          $table->boolean('done')->default(false);
//    then run:  php artisan migrate
// 3. In the model, set protected $fillable = ['title', 'done'];
// 4. In routes/web.php add:
//      Route::get('/tasks',  [TaskController::class, 'index']);
//      Route::post('/tasks', [TaskController::class, 'store']);
// 5. In TaskController@index, fetch Task::all() and return a Blade view
//    that @foreach-es over the tasks.
// 6. In TaskController@store, $request->validate(['title' => 'required'])
//    then Task::create(...) and redirect('/tasks').
//
// ✅ Expected: visiting /tasks lists tasks; posting a title adds one.

// your code here
?>

🎉 Lesson Complete!

Practice quiz

Which command creates a brand-new Laravel project with Composer?

  • composer create-project laravel/laravel myapp
  • php artisan new myapp
  • laravel build myapp
  • composer require laravel

Answer: composer create-project laravel/laravel myapp. composer create-project laravel/laravel myapp downloads the framework and scaffolds a fresh application directory ready to run.

Which architectural pattern does Laravel follow?

  • Event sourcing
  • Pipes and filters
  • MVC (Model-View-Controller)
  • MVVM only

Answer: MVC (Model-View-Controller). Laravel is built around the Model-View-Controller pattern: models hold data, views render output, and controllers coordinate between them.

How do you register a route that answers a GET request for the home page?

  • app()->route('/')
  • Route::get('/', fn () => view('home'))
  • Http::get('/')
  • Router::add('/')

Answer: Route::get('/', fn () => view('home')). Route::get(uri, action) registers a GET route. The action can be a closure or a controller method.

What is Eloquent in Laravel?

  • A queue driver
  • A testing framework
  • A templating engine
  • Its object-relational mapper (ORM)

Answer: Its object-relational mapper (ORM). Eloquent is Laravel's ORM. Each model maps to a database table, and rows become objects you can query and save fluently.

Which Artisan command generates a new database migration?

  • php artisan make:migration create_posts_table
  • php artisan db:new posts
  • composer migrate posts
  • php artisan schema:add posts

Answer: php artisan make:migration create_posts_table. make:migration scaffolds a timestamped migration class where you define the schema in up() and down().

What templating engine does Laravel use for views?

  • Mustache
  • Blade
  • Twig
  • Smarty

Answer: Blade. Blade is Laravel's templating engine. It compiles .blade.php files to plain PHP and adds directives like @if, @foreach, and template inheritance.

Where do you define a one-to-many relationship between User and Post models?

  • In the migration only
  • In config/database.php
  • In the route file with Route::relation
  • As methods using hasMany() / belongsTo() on the Eloquent models

Answer: As methods using hasMany() / belongsTo() on the Eloquent models. Relationships are methods on the models: User has a posts() method returning $this->hasMany(Post::class), and Post has user() returning belongsTo(User::class).

What is the primary job of route middleware in Laravel?

  • To compile Blade templates
  • To seed the database
  • To filter or modify HTTP requests entering the application (e.g. auth checks)
  • To register service providers

Answer: To filter or modify HTTP requests entering the application (e.g. auth checks). Middleware wraps requests, running code before and after the controller. Common uses are authentication, CSRF protection, and rate limiting.

How do you validate incoming request data in a controller?

  • $request->validate(['title' => 'required|max:255'])
  • validate_form($_POST)
  • Route::check($request)
  • $request->guard()

Answer: $request->validate(['title' => 'required|max:255']). $request->validate([...]) checks the input against rules; on failure it automatically redirects back with errors, so the controller body only runs on valid data.

What is a Laravel facade such as Cache or DB?

  • A frontend CSS framework
  • A static-looking proxy that resolves a service out of the service container
  • A database table
  • A Blade directive

Answer: A static-looking proxy that resolves a service out of the service container. Facades give a clean, static-style interface (Cache::get(...)) to objects resolved from the service container, while still being backed by real, testable instances.

Continue this course

Frequently asked questions

Why is Laravel the most popular PHP framework?

Laravel combines a clean, expressive syntax with batteries-included tooling: routing, the Eloquent ORM, Blade templates, the Artisan CLI, queues, authentication scaffolding, and a powerful service container all ship together. It has excellent documentation, a huge ecosystem of first-party packages (Breeze, Sanctum, Horizon, Reverb), and a large community. That mix of developer happiness and productivity is why most new PHP projects start with Laravel.

What is the difference between a migration and a model?

A migration is a versioned PHP file that describes a change to your database schema (creating a table, adding a column). You run migrations with php artisan migrate, and they let your whole team build the same database from code. A model is an Eloquent class that maps to an existing table and lets you read and write rows as objects. In short: migrations define the structure, models work with the data inside it.

What does the Artisan CLI do?

Artisan is Laravel's command-line assistant, run as php artisan <command>. It scaffolds code (make:model, make:controller, make:migration), runs and rolls back migrations (migrate, migrate:rollback), clears caches, starts a dev server (serve), runs queued jobs, and exposes a Tinker REPL for poking at your app. You can also write your own custom commands. It removes a huge amount of boilerplate.

What is the service container and why are facades useful?

The service container is Laravel's tool for dependency injection: it knows how to build your classes and their dependencies, and hands you fully-wired objects. Facades (like Cache, DB, Route) provide a short, static-looking syntax over services resolved from that container, so you write Cache::get('key') instead of manually constructing the cache manager. Despite looking static, facades are backed by real instances and can be swapped or mocked in tests.

Should I use closures or controllers for my routes?

Closures defined directly in routes/web.php are fine for tiny, one-off endpoints and quick prototypes. For anything with real logic, point the route at a controller method instead: [PostController::class, 'show']. Controllers keep related actions together, are easier to test, and let Laravel cache your routes for performance. As a rule of thumb, graduate a route from a closure to a controller as soon as it does more than return a simple view.

Related lessons