Laravel Complete Tutorial: Beginner to Advanced (27 Topics with Code Examples) — a practical guide to Laravel complete tutorial with clear examples you can reuse in real projects.
This complete Laravel tutorial walks you through 27 essential topics — from installation to route model binding — with short explanations and clean, production-quality code examples. It is written for beginners, but useful as a quick reference for working Laravel developers.
What you will learn
- 1. Introduction to Laravel and Installation
- 2. Directory Structure and Routing
- 3. Blade Templates
- 4. Controllers in Laravel
- 5. Submitting Form Data and Validation
- 6. Blade Components
- 7. Database Configuration and Migrations
- 8. Creating Tables Using Migrations
- 9. Models in Laravel (Eloquent)
- 10. Querying with Eloquent ORM
- 11. Routing Through Buttons and Anchor Tags
- 12. Configuring Custom Helpers
- 13. Accessors and Mutators
- 14. Handling Sessions
- 15. Soft Deletes
- 16. Converting an HTML Template into a Laravel Project
- 17. HTML Package in Laravel
- 18. File Uploading, Seeders, and Faker
- 19. Searching and Pagination
- 20. Grouping Routes
- 21. Localization
- 22. Stubs in Laravel
- 23. Laravel Migrations Deep Dive
- 24. One-to-One and One-to-Many Relationships
- 25. Middleware
- 26. Custom Artisan Commands
- 27. Route Model Binding
Tip: Follow the topics in order if you are new to Laravel. Jump to any section from the list above if you already know the basics.
1. Introduction to Laravel and Installation
Short description
Laravel is a modern PHP framework that helps you build web applications faster with clean structure, routing, Blade templates, Eloquent ORM, validation, queues, and testing tools.
Steps
- Install PHP 8.2+ and Composer on your machine.
- Create a new Laravel project with Composer.
- Generate the application key and start the local server.
Create a Laravel project
composer create-project laravel/laravel blog-app
cd blog-app
php artisan key:generate
php artisan serve
Default welcome route
<?php
use IlluminateSupportFacadesRoute;
Route::get('/', function () {
return view('welcome');
});
2. Directory Structure and Routing
Short description
Laravel keeps code organized. Routes map URLs to closures or controllers so browsers can open pages and APIs.
Steps
- Remember key folders:
app/,routes/,resources/views/,database/,config/, andpublic/. - Define routes in
routes/web.phpfor browser pages. - Prefer named routes so links stay stable when URLs change.
Basic and named routes
use AppHttpControllersPostController;
use IlluminateSupportFacadesRoute;
Route::get('/about', fn () => 'About page');
Route::get('/users/{id}', function (string $id) {
return "User ID: {$id}";
})->name('users.show');
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
3. Blade Templates
Short description
Blade is Laravel’s templating engine. It supports layouts, escaping, loops, and conditions while compiling to plain PHP.
Steps
- Create a shared layout with
@yield. - Extend the layout from each page with
@extendsand@section. - Always escape user input with
{{ }}unless the HTML is trusted.
Layout + child view
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head><title>@yield('title', 'My App')</title></head>
<body>
<main>@yield('content')</main>
</body>
</html>
{{-- resources/views/home.blade.php --}}
@extends('layouts.app')
@section('title', 'Home')
@section('content')
<h1>Welcome, {{ $name }}</h1>
@forelse ($posts as $post)
<article>{{ $post->title }}</article>
@empty
<p>No posts yet.</p>
@endforelse
@endsection
4. Controllers in Laravel
Short description
Controllers group request-handling logic so `routes/web.php` stays thin and readable.
Steps
- Generate a controller with Artisan.
- Move page logic into controller methods.
- Use resource controllers for standard CRUD routes.
Create and use a controller
php artisan make:controller PostController
php artisan make:controller ProductController --resource
Controller example
<?php
namespace AppHttpControllers;
use AppModelsPost;
use IlluminateHttpRequest;
class PostController extends Controller
{
public function index()
{
$posts = Post::latest()->get();
return view('posts.index', compact('posts'));
}
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
public function store(Request $request)
{
// validate + save
}
}
Route wiring
use AppHttpControllersPostController;
use AppHttpControllersProductController;
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::post('/posts', [PostController::class, 'store']);
Route::resource('products', ProductController::class);
5. Submitting Form Data and Validation
Short description
Laravel protects forms with CSRF tokens and validates input before you save anything to the database.
Steps
- Add
@csrfinside every POST/PUT/PATCH/DELETE form. - Validate with
$request->validate()or a Form Request class. - Redisplay old input and error messages after failed validation.
Blade form
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<input type="text" name="title" value="{{ old('title') }}">
@error('title') <span>{{ $message }}</span> @enderror
<textarea name="body">{{ old('body') }}</textarea>
@error('body') <span>{{ $message }}</span> @enderror
<button type="submit">Save</button>
</form>
Controller validation
public function store(Request $request)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string', 'min:10'],
]);
Post::create($validated);
return redirect()
->route('posts.index')
->with('success', 'Post created');
}
Form Request (cleaner)
// php artisan make:request StorePostRequest
public function rules(): array
{
return [
'title' => ['required', 'max:255'],
'body' => ['required'],
];
}
public function store(StorePostRequest $request)
{
Post::create($request->validated());
return back()->with('success', 'Saved');
}
6. Blade Components
Short description
Components let you reuse UI pieces (alerts, buttons, cards) with props and slots instead of copying HTML.
Steps
- Generate a class-based component or create an anonymous Blade component.
- Pass props and content slots from parent views.
- Merge attributes for flexible CSS classes.
Alert component
{{-- php artisan make:component Alert --}}
{{-- resources/views/components/alert.blade.php --}}
<div class="alert alert-{{ $type }}">
{{ $slot }}
</div>
{{-- Usage --}}
<x-alert type="success">
Profile updated successfully.
</x-alert>
Anonymous button component
{{-- resources/views/components/button.blade.php --}}
<button {{ $attributes->merge(['class' => 'btn']) }}>
{{ $slot }}
</button>
<x-button class="btn-primary" type="submit">Save</x-button>
7. Database Configuration and Migrations
Short description
Migrations are version-controlled PHP files that create and change database tables safely across local, staging, and production.
Steps
- Set database credentials in
.env. - Create migrations with Artisan.
- Run
php artisan migrateto apply schema changes.
.env database settings
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog_app
DB_USERNAME=root
DB_PASSWORD=
Migration skeleton
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
Useful migrate commands
php artisan migrate
php artisan migrate:status
php artisan migrate:rollback
8. Creating Tables Using Migrations
Short description
Use Blueprint helpers to define columns, defaults, unique indexes, foreign keys, and soft deletes when creating tables.
Steps
- Create a migration for the new table.
- Define columns and constraints clearly.
- Use a separate migration when altering an existing table.
Rich posts table
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->boolean('is_published')->default(false);
$table->timestamp('published_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
Alter an existing table
// php artisan make:migration add_excerpt_to_posts_table --table=posts
Schema::table('posts', function (Blueprint $table) {
$table->string('excerpt')->nullable()->after('title');
});
9. Models in Laravel (Eloquent)
Short description
An Eloquent model represents a database table. (This is often written as “Modal” by mistake — in Laravel it is the Model.)
Steps
- Generate a model (optionally with a migration).
- Set
$fillable(or$guarded) for mass assignment safety. - Cast attributes like booleans and dates.
Create model + migration
php artisan make:model Post -m
Post model
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Post extends Model
{
protected $fillable = [
'user_id',
'title',
'slug',
'body',
'is_published',
];
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
];
}
Create and update records
Post::create([
'user_id' => auth()->id(),
'title' => 'Hello Laravel',
'slug' => 'hello-laravel',
'body' => 'My first post',
]);
$post = Post::findOrFail(1);
$post->update(['title' => 'Updated title']);
10. Querying with Eloquent ORM
Short description
Eloquent gives a fluent API for everyday SQL: find, filter, sort, paginate, and eager-load related data.
Steps
- Start with simple
all(),find(), andwhere()queries. - Chain conditions for readable filters.
- Use
with()to avoid N+1 query problems.
Common Eloquent queries
$all = Post::all();
$one = Post::find(1);
$post = Post::where('slug', 'hello-laravel')->first();
$published = Post::where('is_published', true)
->orderByDesc('published_at')
->limit(10)
->get();
$results = Post::where('title', 'like', '%laravel%')->get();
$posts = Post::with('user')->latest()->get();
11. Routing Through Buttons and Anchor Tags
Short description
Use the `route()` helper in Blade so links and buttons always point to the correct named route.
Steps
- Name your routes.
- Generate URLs with
route('name', $model). - Use method spoofing (
@method) for PUT/PATCH/DELETE from HTML forms.
Links and delete button
<a href="{{ route('posts.index') }}">All posts</a>
<a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
<a class="btn" href="{{ route('posts.create') }}">Create post</a>
<form action="{{ route('posts.destroy', $post) }}" method="POST" onsubmit="return confirm('Delete?')">
@csrf
@method('DELETE')
<button type="submit">Delete</button>
</form>
12. Configuring Custom Helpers
Short description
Custom helpers are small global PHP functions for formatting and other utilities you reuse across controllers and Blade views.
Steps
- Create
app/helpers.php. - Register it under Composer
autoload.files. - Run
composer dump-autoload.
helpers.php
<?php
if (! function_exists('format_inr')) {
function format_inr(float $amount): string
{
return '₹' . number_format($amount, 2);
}
}
composer.json autoload
{
"autoload": {
"psr-4": { "App": "app/" },
"files": ["app/helpers.php"]
}
}
Usage
<p>Total: {{ format_inr(1299.5) }}</p>
13. Accessors and Mutators
Short description
Accessors format values when you read them. Mutators transform values before they are saved to the database.
Steps
- Use the Attribute API on your Eloquent model.
- Define
getand/orsetcallbacks. - Append computed attributes when needed for APIs/views.
Title accessor + mutator
use IlluminateDatabaseEloquentCastsAttribute;
protected function title(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
$post->title = 'HELLO WORLD'; // stored as "hello world"
echo $post->title; // reads as "Hello world"
14. Handling Sessions
Short description
Sessions store temporary data across requests — flash messages, cart counts, locale preferences, and more.
Steps
- Store and read values with the
session()helper. - Flash success messages for the next request only.
- Choose a session driver in
.env(file,database, orredis).
Session store, flash, and clear
session(['cart_count' => 3]);
$count = session('cart_count', 0);
return redirect()
->route('posts.index')
->with('success', 'Post created successfully');
session()->forget('cart_count');
session()->flush();
Flash message in Blade
@if (session('success'))
<div class="alert">{{ session('success') }}</div>
@endif
15. Soft Deletes
Short description
Soft deletes mark a row as deleted (`deleted_at`) without permanently removing it, so you can restore later.
Steps
- Add
$table->softDeletes()in a migration. - Use the
SoftDeletestrait on the model. - Query trashed records with
onlyTrashed()/withTrashed().
Soft delete setup and usage
use IlluminateDatabaseEloquentSoftDeletes;
class Post extends Model
{
use SoftDeletes;
}
$post->delete(); // soft delete
$post->restore(); // undo
$post->forceDelete(); // permanent
$trashed = Post::onlyTrashed()->get();
$withTrashed = Post::withTrashed()->find(1);
16. Converting an HTML Template into a Laravel Project
Short description
Turn a static HTML template into Laravel by extracting a Blade layout, splitting pages into views, and wiring Vite assets.
Steps
- Create a Laravel project and copy CSS/JS/images into
resources/andpublic/. - Extract header/footer into a layout and partials.
- Replace each HTML page with a Blade view and named route.
- Swap hard-coded paths for
route()andasset()/@vite.
Layout extraction
<!DOCTYPE html>
<html>
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<title>@yield('title')</title>
</head>
<body>
@include('partials.header')
@yield('content')
@include('partials.footer')
</body>
</html>
@extends('layouts.app')
@section('title', 'Services')
@section('content')
<h1>Our Services</h1>
@endsection
17. HTML Package in Laravel
Short description
Older projects may use `laravelcollective/html` form helpers. New Laravel apps usually prefer plain Blade forms and components.
Steps
- Know Collective HTML if you maintain legacy code.
- Prefer native Blade + CSRF for new projects.
- Keep form markup consistent with your design system.
Legacy Collective form
{!! Form::open(['route' => 'posts.store']) !!}
{!! Form::label('title', 'Title') !!}
{!! Form::text('title', old('title'), ['class' => 'form-control']) !!}
{!! Form::submit('Save') !!}
{!! Form::close() !!}
Modern Blade alternative (recommended)
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<input name="title" value="{{ old('title') }}">
<button type="submit">Save</button>
</form>
18. File Uploading, Seeders, and Faker
Short description
Upload user files to storage, and use factories/Faker plus seeders to fill your development database with realistic demo data.
Steps
- Validate and store uploads on the
publicdisk. - Run
php artisan storage:linkso files are web-accessible. - Create factories and seeders for demo records.
File upload
public function store(Request $request)
{
$request->validate([
'avatar' => ['required', 'image', 'max:2048'],
]);
$path = $request->file('avatar')->store('avatars', 'public');
auth()->user()->update(['avatar_path' => $path]);
return back()->with('success', 'Uploaded');
}
Factory + seeder
// PostFactory
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraphs(3, true),
];
}
// PostSeeder
Post::factory()->count(50)->create();
// php artisan db:seed --class=PostSeeder
19. Searching and Pagination
Short description
Search filters results with query parameters. Pagination splits large lists into pages with Laravel’s built-in paginator.
Steps
- Read the search term from the request.
- Apply a conditional
where/likefilter. - Use
paginate()and keep query strings withwithQueryString().
Search + paginate controller
public function index(Request $request)
{
$q = $request->string('q')->toString();
$posts = Post::query()
->when($q !== '', function ($query) use ($q) {
$query->where(function ($inner) use ($q) {
$inner->where('title', 'like', "%{$q}%")
->orWhere('body', 'like', "%{$q}%");
});
})
->latest()
->paginate(10)
->withQueryString();
return view('posts.index', compact('posts', 'q'));
}
Blade search UI
<form method="GET" action="{{ route('posts.index') }}">
<input type="search" name="q" value="{{ $q }}">
<button type="submit">Search</button>
</form>
@foreach ($posts as $post)
<h2>{{ $post->title }}</h2>
@endforeach
{{ $posts->links() }}
20. Grouping Routes
Short description
Route groups apply shared settings — prefix, name, middleware, or controller — to many routes at once.
Steps
- Group admin routes under a prefix and name.
- Apply auth middleware to protected areas.
- Use controller groups to reduce repetition.
Route groups
Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
Route::resource('posts', AdminPostController::class);
});
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/profile', [ProfileController::class, 'edit']);
});
Route::controller(PostController::class)->group(function () {
Route::get('/posts', 'index');
Route::post('/posts', 'store');
});
21. Localization
Short description
Localization stores language strings in files and displays the correct language based on the active locale.
Steps
- Create language files under
lang/. - Use
__()or@langin Blade. - Set the locale from config, session, or middleware.
Language files and usage
// lang/en/messages.php
return ['welcome' => 'Welcome to our site'];
// lang/hi/messages.php
return ['welcome' => 'हमारी साइट पर आपका स्वागत है'];
App::setLocale('hi');
// or
session(['locale' => 'hi']);
app()->setLocale(session('locale', 'en'));
Blade translation
<h1>{{ __('messages.welcome') }}</h1>
22. Stubs in Laravel
Short description
Stubs are templates Artisan uses when generating classes. Customize them so every new file matches your team standards.
Steps
- Publish stubs with
php artisan stub:publish. - Edit files in the
/stubsdirectory. - Generate models/controllers as usual — Artisan uses your stubs.
Publish and use stubs
php artisan stub:publish
# edit stubs/model.stub (e.g. always include SoftDeletes)
php artisan make:model Comment
23. Laravel Migrations Deep Dive
Short description
Go beyond “create table”: learn rollbacks, batches, raw SQL, and safe habits for production deploys.
Steps
- Use rollback carefully and test on staging.
- Prefer additive, reversible migrations.
- Never run
migrate:freshon production.
Migration commands and tips
php artisan migrate
php artisan migrate:rollback
php artisan migrate:rollback --step=1
php artisan migrate:status
# Dev only:
php artisan migrate:fresh --seed
Raw statement example
DB::statement('ALTER TABLE posts ADD FULLTEXT(title, content)');
24. One-to-One and One-to-Many Relationships
Short description
Eloquent relationships describe how models connect — for example, a user has one profile and many posts.
Steps
- Define
hasOne/belongsTofor one-to-one. - Define
hasMany/belongsTofor one-to-many. - Eager-load relationships with
with()when listing records.
One-to-one
// User.php
public function profile()
{
return $this->hasOne(Profile::class);
}
// Profile.php
public function user()
{
return $this->belongsTo(User::class);
}
One-to-many
// User.php
public function posts()
{
return $this->hasMany(Post::class);
}
// Post.php
public function user()
{
return $this->belongsTo(User::class);
}
$user->posts()->create([
'title' => 'New post',
'body' => 'Content here',
]);
Post::with('user')->get();
25. Middleware
Short description
Middleware filters HTTP requests before they reach your controller — perfect for auth, roles, and custom checks.
Steps
- Create middleware with Artisan.
- Return
$next($request)when allowed, or abort/redirect when not. - Attach middleware to routes or route groups.
Admin middleware
// php artisan make:middleware EnsureIsAdmin
public function handle(Request $request, Closure $next)
{
if (! $request->user()?->is_admin) {
abort(403);
}
return $next($request);
}
Route::get('/admin', [AdminController::class, 'index'])
->middleware(['auth', EnsureIsAdmin::class]);
26. Custom Artisan Commands
Short description
Custom Artisan commands automate maintenance tasks, imports, and reports from the command line — and can be scheduled.
Steps
- Generate a command class.
- Define
$signatureand$description. - Implement
handle()and optionally schedule the command.
Custom command
// php artisan make:command SendWeeklyReport
protected $signature = 'report:weekly {--email=}';
protected $description = 'Send the weekly report';
public function handle(): int
{
$email = $this->option('email') ?: config('mail.from.address');
$this->info("Sending weekly report to {$email}");
// Report logic...
$this->info('Done');
return self::SUCCESS;
}
// php artisan report:weekly --email=admin@example.com
// Schedule::command('report:weekly')->weeklyOn(1, '9:00');
27. Route Model Binding
Short description
Route model binding injects an Eloquent model into your route/controller automatically — no manual `findOrFail` for every show page.
Steps
- Type-hint the model in the controller method.
- Optionally bind by
slugwithgetRouteKeyName(). - Use custom bindings when you need extra query constraints.
Implicit binding
Route::get('/posts/{post}', [PostController::class, 'show']);
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
Bind by slug + custom binding
public function getRouteKeyName(): string
{
return 'slug';
}
Route::bind('post', function (string $value) {
return Post::where('slug', $value)
->where('is_published', true)
->firstOrFail();
});
Conclusion
You now have a practical path through Laravel’s core features: install the framework, define routes, build Blade views and components, validate forms, work with migrations and Eloquent, secure requests with middleware, and automate work with Artisan. Practice each topic in a small demo project — that is the fastest way to remember Laravel’s patterns.
Next steps: build a blog or small CRUD app that uses forms, validation, relationships, soft deletes, search, and pagination together. Once that feels comfortable, explore queues, jobs, policies, and testing.