Getting started
Installation
# Create new Laravel project
laravel new example-app
# Or via Composer
composer create-project laravel/laravel example-app
Requirements
| Requirement | Version |
|---|---|
| PHP | 8.1+ |
| Composer | Latest |
| Database | SQLite (default) |
Development server
# Start development server
php artisan serve
# Or
composer run dev
# Runs on http://localhost:8000
Quick example
// routes/web.php
Route::get('/hello', function () {
return view('hello', ['name' => 'World']);
});
// resources/views/hello.blade.php
<h1>Hello, {{ $name }}</h1>
Routing
Basic routes
// routes/web.php
Route::get('/user', [UserController::class, 'index']);
Route::post('/user', [UserController::class, 'store']);
Route::put('/user/{id}', [UserController::class, 'update']);
Route::patch('/user/{id}', [UserController::class, 'patch']);
Route::delete('/user/{id}', [UserController::class, 'destroy']);
Route parameters
// Required parameter
Route::get('/user/{id}', function ($id) {
return "User $id";
});
// Optional parameter
Route::get('/user/{name?}', function ($name = 'Guest') {
return "Hello, $name";
});
// Multiple parameters
Route::get('/post/{id}/comment/{commentId}', function ($id, $commentId) {
return "Post $id, Comment $commentId";
});
Named routes
// Define named route
Route::get('/profile', [ProfileController::class, 'show'])
->name('profile');
// Generate URL to named route
$url = route('profile');
// Redirect to named route
return redirect()->route('profile');
// With parameters
Route::get('/user/{id}', [UserController::class, 'show'])
->name('user.show');
$url = route('user.show', ['id' => 1]);
Route groups
// Middleware
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/settings', [SettingsController::class, 'index']);
});
// Prefix
Route::prefix('admin')->group(function () {
Route::get('/users', [AdminController::class, 'users']);
Route::get('/posts', [AdminController::class, 'posts']);
});
// Combined
Route::prefix('admin')
->middleware(['auth', 'admin'])
->group(function () {
Route::get('/dashboard', [AdminController::class, 'index']);
});
Route model binding
// Automatic binding
Route::get('/user/{user}', function (User $user) {
return $user->email;
});
// Custom key
Route::get('/post/{post:slug}', function (Post $post) {
return $post->title;
});
// Model configuration
class Post extends Model {
public function getRouteKeyName() {
return 'slug';
}
}
Available methods
| Method | Description |
|---|---|
Route::get($uri, $callback) |
GET route |
Route::post($uri, $callback) |
POST route |
Route::put($uri, $callback) |
PUT route |
Route::patch($uri, $callback) |
PATCH route |
Route::delete($uri, $callback) |
DELETE route |
Route::any($uri, $callback) |
Any method |
Route::match(['get', 'post'], $uri, $callback) |
Specific methods |
Route::redirect($from, $to) |
Redirect |
Eloquent ORM
Creating models
# Create model
php artisan make:model Flight
# With migration
php artisan make:model Flight -m
# With controller
php artisan make:model Flight -c
# All together
php artisan make:model Flight -mcr
Model definition
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
// Table name (optional, auto-inferred)
protected $table = 'flights';
// Primary key (optional, defaults to 'id')
protected $primaryKey = 'flight_id';
// Mass assignable attributes
protected $fillable = ['name', 'airline'];
// Guarded attributes
protected $guarded = ['price'];
// Timestamps (enabled by default)
public $timestamps = true;
}
Retrieving models
// All records
$flights = Flight::all();
// Find by primary key
$flight = Flight::find(1);
// Find or fail (throws 404)
$flight = Flight::findOrFail(1);
// First record
$flight = Flight::first();
// Where clause
$flights = Flight::where('active', 1)->get();
// Multiple conditions
$flights = Flight::where('active', 1)
->where('destination', 'Paris')
->get();
// First or fail
$flight = Flight::where('number', 'FR900')->firstOrFail();
Creating & updating
// Create
$flight = new Flight;
$flight->name = 'Paris to London';
$flight->save();
// Mass assignment
$flight = Flight::create([
'name' => 'Paris to London',
'airline' => 'Air France'
]);
// Update
$flight = Flight::find(1);
$flight->name = 'New name';
$flight->save();
// Update via query
Flight::where('active', 1)
->update(['delayed' => 1]);
// Find or create
$flight = Flight::firstOrCreate(
['name' => 'Tokyo to Sydney'],
['airline' => 'Qantas']
);
// Update or create
$flight = Flight::updateOrCreate(
['name' => 'Tokyo to Sydney'],
['airline' => 'Qantas']
);
Deleting models
// Delete instance
$flight = Flight::find(1);
$flight->delete();
// Delete by ID
Flight::destroy(1);
Flight::destroy([1, 2, 3]);
// Delete via query
Flight::where('active', 0)->delete();
// Soft deletes (requires SoftDeletes trait)
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model {
use SoftDeletes;
}
Relationships
// One to one (hasOne)
class User extends Model {
public function phone() {
return $this->hasOne(Phone::class);
}
}
$phone = User::find(1)->phone;
// One to many (hasMany)
class Post extends Model {
public function comments() {
return $this->hasMany(Comment::class);
}
}
$comments = Post::find(1)->comments;
// Belongs to (inverse)
class Comment extends Model {
public function post() {
return $this->belongsTo(Post::class);
}
}
$post = Comment::find(1)->post;
// Many to many (belongsToMany)
class User extends Model {
public function roles() {
return $this->belongsToMany(Role::class);
}
}
$roles = User::find(1)->roles;
Query methods
| Method | Description |
|---|---|
all() |
Get all records |
find($id) |
Find by ID |
first() |
First record |
get() |
Execute query |
where($col, $val) |
Where clause |
orderBy($col) |
Order by |
limit($n) |
Limit results |
skip($n) |
Skip records |
count() |
Count records |
pluck($col) |
Get column values |
Blade Templates
Displaying data
// Escaped output
Hello, {{ $name }}
// Raw output (unescaped)
{!! $htmlContent !!}
// Default value
{{ $name ?? 'Guest' }}
// JSON encoding
<script>
var app = @json($array);
</script>
Control structures
// If statements
@if ($records)
<p>Records found</p>
@elseif ($pending)
<p>Pending records</p>
@else
<p>No records</p>
@endif
// Unless
@unless (Auth::check())
<p>You are not signed in</p>
@endunless
// For loop
@for ($i = 0; $i < 10; $i++)
<p>Item {{ $i }}</p>
@endfor
// Foreach
@foreach ($users as $user)
<p>{{ $user->name }}</p>
@endforeach
// Forelse (with empty case)
@forelse ($users as $user)
<p>{{ $user->name }}</p>
@empty
<p>No users</p>
@endforelse
// While
@while (true)
<p>Looping</p>
@endwhile
Loop variable
@foreach ($users as $user)
@if ($loop->first)
<p>First iteration</p>
@endif
<p>{{ $loop->index }}: {{ $user->name }}</p>
@if ($loop->last)
<p>Last iteration</p>
@endif
@endforeach
| Property | Description |
|---|---|
$loop->index |
Current index (0-based) |
$loop->iteration |
Current iteration (1-based) |
$loop->remaining |
Iterations remaining |
$loop->count |
Total items |
$loop->first |
First iteration |
$loop->last |
Last iteration |
$loop->even |
Even iteration |
$loop->odd |
Odd iteration |
$loop->depth |
Nesting level |
$loop->parent |
Parent loop variable |
Template inheritance
// Layout (resources/views/layouts/app.blade.php)
<!DOCTYPE html>
<html>
<head>
<title>@yield('title')</title>
</head>
<body>
@yield('content')
@section('sidebar')
<p>Default sidebar</p>
@show
</body>
</html>
// Child view
@extends('layouts.app')
@section('title', 'Page Title')
@section('content')
<p>This is the page content</p>
@endsection
@section('sidebar')
@parent
<p>Appended to sidebar</p>
@endsection
Components
// Anonymous component (resources/views/components/alert.blade.php)
<div class="alert alert-{{ $type }}">
{{ $slot }}
</div>
// Usage
<x-alert type="success">
Operation completed!
</x-alert>
// With attributes
<x-alert type="error" class="mb-4">
Error occurred!
</x-alert>
// Named slots
<x-card>
<x-slot:title>
Card Title
</x-slot>
Card content
</x-card>
Comments
{{-- This comment will not be in HTML --}}
<!-- This comment will be in HTML -->
Directives
| Directive | Description |
|---|---|
@csrf |
CSRF token field |
@method('PUT') |
HTTP method field |
@auth |
If authenticated |
@guest |
If guest |
@error('field') |
Validation error |
@include('view') |
Include view |
@includeIf('view') |
Include if exists |
@each('view', $items, 'item') |
Iterate include |
Validation
Basic validation
// In controller
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
'email' => 'required|email|unique:users',
]);
// Validation passed, use $validated
}
Validation rules
// Multiple rules
'email' => 'required|email|unique:users,email'
// Array syntax
'email' => ['required', 'email', 'unique:users']
// Numeric rules
'age' => 'required|numeric|min:18|max:120'
// String rules
'username' => 'required|string|min:3|max:20|alpha_dash'
// File rules
'photo' => 'required|image|mimes:jpeg,png|max:2048'
// Date rules
'birthday' => 'required|date|before:today'
// Conditional rules
'email' => 'required_if:type,subscriber|email'
Common rules
| Rule | Description |
|---|---|
required |
Field must be present |
email |
Valid email format |
unique:table,column |
Unique in database |
exists:table,column |
Exists in database |
max:value |
Maximum value/length |
min:value |
Minimum value/length |
numeric |
Must be numeric |
integer |
Must be integer |
string |
Must be string |
confirmed |
Must match field_confirmation |
alpha |
Only letters |
alpha_num |
Letters and numbers |
alpha_dash |
Letters, numbers, dashes, underscores |
url |
Valid URL |
date |
Valid date |
image |
Must be image |
mimes:jpg,png |
Specific MIME types |
Displaying errors
// In Blade template
@error('email')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
// All errors
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
// Old input
<input type="text" name="email" value="{{ old('email') }}">
Form request validation
# Create form request
php artisan make:request StorePostRequest
// app/Http/Requests/StorePostRequest.php
class StorePostRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required|max:255',
'body' => 'required',
];
}
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
}
// In controller
public function store(StorePostRequest $request)
{
// Validation automatically applied
$validated = $request->validated();
}
Artisan Commands
Make commands
# Model
php artisan make:model Post
php artisan make:model Post -m # with migration
# Controller
php artisan make:controller PostController
php artisan make:controller PostController --resource
# Migration
php artisan make:migration create_posts_table
# Seeder
php artisan make:seeder PostSeeder
# Factory
php artisan make:factory PostFactory
# Request
php artisan make:request StorePostRequest
# Middleware
php artisan make:middleware EnsureTokenIsValid
# Command
php artisan make:command SendEmails
Database commands
# Run migrations
php artisan migrate
# Rollback last migration
php artisan migrate:rollback
# Rollback all migrations
php artisan migrate:reset
# Fresh migration (drop all)
php artisan migrate:fresh
# Fresh with seeding
php artisan migrate:fresh --seed
# Run seeders
php artisan db:seed
# Run specific seeder
php artisan db:seed --class=UserSeeder
Cache commands
# Clear application cache
php artisan cache:clear
# Clear route cache
php artisan route:clear
# Cache routes
php artisan route:cache
# Clear config cache
php artisan config:clear
# Cache config
php artisan config:cache
# Clear view cache
php artisan view:clear
# Clear all caches
php artisan optimize:clear
Utility commands
# Start tinker (REPL)
php artisan tinker
# List all routes
php artisan route:list
# Run queue worker
php artisan queue:work
# Run scheduler
php artisan schedule:run
# Generate app key
php artisan key:generate
# Create storage link
php artisan storage:link
# Server
php artisan serve
php artisan serve --port=8080
Request & Response
Request input
// Get input value
$name = $request->input('name');
// With default
$name = $request->input('name', 'Guest');
// All input
$input = $request->all();
// Only specific fields
$input = $request->only(['username', 'password']);
// Except fields
$input = $request->except(['credit_card']);
// Query parameters
$name = $request->query('name');
// Check if present
if ($request->has('name')) {
//
}
// Check if filled
if ($request->filled('name')) {
//
}
Request methods
// Get request method
$method = $request->method();
// Check method
if ($request->isMethod('post')) {
//
}
// Get URL
$url = $request->url();
// Get full URL
$url = $request->fullUrl();
// Get path
$path = $request->path();
File uploads
// Get uploaded file
$file = $request->file('photo');
// Check if uploaded
if ($request->hasFile('photo')) {
//
}
// Check if valid
if ($request->file('photo')->isValid()) {
//
}
// Store file
$path = $request->file('photo')->store('photos');
// Store with custom name
$path = $request->file('photo')->storeAs('photos', 'custom.jpg');
// Store to public disk
$path = $request->file('photo')->store('photos', 'public');
Responses
// Return view
return view('welcome');
// With data
return view('welcome', ['name' => 'John']);
// Redirect
return redirect('/home');
// Redirect to route
return redirect()->route('profile');
// Redirect with data
return redirect('/home')->with('status', 'Success!');
// JSON response
return response()->json([
'name' => 'John',
'status' => 'active'
]);
// Download response
return response()->download($pathToFile);
// File response
return response()->file($pathToFile);
Database & Migrations
Creating migrations
# Create migration
php artisan make:migration create_flights_table
# Create migration for existing table
php artisan make:migration add_votes_to_users_table
Migration structure
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFlightsTable extends Migration
{
public function up()
{
Schema::create('flights', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('airline');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('flights');
}
}
Column types
$table->id(); // Auto-incrementing ID
$table->string('name'); // VARCHAR
$table->string('name', 100); // VARCHAR(100)
$table->text('description'); // TEXT
$table->integer('votes'); // INTEGER
$table->bigInteger('votes'); // BIGINT
$table->decimal('amount', 8, 2); // DECIMAL
$table->boolean('confirmed'); // BOOLEAN
$table->date('created_date'); // DATE
$table->datetime('created_at'); // DATETIME
$table->timestamp('added_on'); // TIMESTAMP
$table->json('options'); // JSON
$table->timestamps(); // created_at & updated_at
$table->softDeletes(); // deleted_at
Column modifiers
$table->string('email')->nullable();
$table->string('name')->default('Guest');
$table->integer('votes')->unsigned();
$table->string('email')->unique();
$table->integer('user_id')->index();
$table->decimal('amount')->unsigned()->default(0);
Indexes & constraints
// Primary key
$table->primary('id');
// Unique
$table->unique('email');
// Index
$table->index('user_id');
// Foreign key
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
// Composite index
$table->index(['user_id', 'created_at']);
// Drop index
$table->dropIndex('users_email_unique');
Modifying tables
// Add columns
Schema::table('users', function (Blueprint $table) {
$table->string('avatar')->nullable();
});
// Rename column
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('from', 'to');
});
// Drop column
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('votes');
});
// Drop multiple columns
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['votes', 'avatar']);
});
Authentication
Laravel Breeze
# Install Breeze (starter kit)
composer require laravel/breeze --dev
# Install Breeze scaffolding
php artisan breeze:install
# With Blade
php artisan breeze:install blade
# With React
php artisan breeze:install react
# With Vue
php artisan breeze:install vue
# Run migrations
php artisan migrate
npm install && npm run dev
Auth helpers
// Check if authenticated
if (Auth::check()) {
// User is logged in
}
// Get authenticated user
$user = Auth::user();
// Get user ID
$id = Auth::id();
// Authenticate user
Auth::login($user);
// Logout
Auth::logout();
// Attempt login
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// Authentication passed
}
Route protection
// Protect routes with middleware
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
});
// Single route
Route::get('/profile', [ProfileController::class, 'show'])
->middleware('auth');
// In controller constructor
public function __construct()
{
$this->middleware('auth');
}
Sanctum (API tokens)
# Install Sanctum
composer require laravel/sanctum
# Publish config
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
# Run migrations
php artisan migrate
// Create token
$token = $user->createToken('token-name')->plainTextToken;
// Protect routes
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
Examples
Basic CRUD controller
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
$posts = Post::all();
return view('posts.index', compact('posts'));
}
public function create()
{
return view('posts.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
$post = Post::create($validated);
return redirect()->route('posts.show', $post)
->with('success', 'Post created!');
}
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
$post->update($validated);
return redirect()->route('posts.show', $post)
->with('success', 'Post updated!');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')
->with('success', 'Post deleted!');
}
}
Eloquent relationship query
// One-to-many relationship
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
// Query with relationships
$user = User::with('posts')->find(1);
// Eager load multiple relationships
$posts = Post::with(['user', 'comments'])->get();
// Nested eager loading
$posts = Post::with('comments.user')->get();
// Lazy eager loading
$posts = Post::all();
$posts->load('comments');
// Constraining eager loads
$users = User::with(['posts' => function ($query) {
$query->where('published', 1)->orderBy('created_at', 'desc');
}])->get();
// Count relationships
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
Blade template with loop
{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')
@section('title', 'All Posts')
@section('content')
<div class="container">
<h1>All Posts</h1>
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@forelse ($posts as $post)
<article class="post">
<h2>
<a href="{{ route('posts.show', $post) }}">
{{ $post->title }}
</a>
</h2>
<p>{{ Str::limit($post->body, 200) }}</p>
<div class="meta">
<span>By {{ $post->user->name }}</span>
<span>{{ $post->created_at->diffForHumans() }}</span>
<span>{{ $post->comments_count }} comments</span>
</div>
@if ($loop->last)
<p>Last post</p>
@endif
</article>
@empty
<p>No posts found.</p>
@endforelse
{{ $posts->links() }}
</div>
@endsection
Form validation example
{{-- resources/views/posts/create.blade.php --}}
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Create New Post</h1>
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
name="title"
id="title"
class="form-control @error('title') is-invalid @enderror"
value="{{ old('title') }}"
>
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea
name="body"
id="body"
class="form-control @error('body') is-invalid @enderror"
rows="10"
>{{ old('body') }}</textarea>
@error('body')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">
Create Post
</button>
</form>
</div>
@endsection
// Controller with validation
public function store(Request $request)
{
$validated = $request->validate([
'title' => [
'required',
'string',
'max:255',
'unique:posts,title'
],
'body' => [
'required',
'string',
'min:50'
],
], [
'title.required' => 'Please provide a post title.',
'title.unique' => 'This title has already been used.',
'body.min' => 'Post body must be at least 50 characters.',
]);
$post = auth()->user()->posts()->create($validated);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully!');
}
Gotchas
Mass assignment protection
Always define $fillable or $guarded properties to prevent mass assignment vulnerabilities:
class User extends Model
{
// Allow these fields only
protected $fillable = ['name', 'email', 'password'];
// Or guard specific fields
protected $guarded = ['is_admin'];
}
N+1 query problem
Use eager loading to avoid N+1 queries:
// Bad - N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // Executes query for each post
}
// Good - 2 queries total
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name;
}
Route caching
Route caching doesn't work with closure-based routes. Use controllers:
// Won't work with route:cache
Route::get('/', function () {
return view('welcome');
});
// Use controllers instead
Route::get('/', [HomeController::class, 'index']);
Config caching
After running config:cache, environment variables won't be loaded. Use config() helper:
// Bad - won't work with config:cache
$apiKey = env('API_KEY');
// Good - works with config:cache
$apiKey = config('services.api.key');
Timezone handling
Laravel stores dates in UTC by default. Set timezone in config/app.php:
'timezone' => 'UTC', // or 'America/New_York', etc.
Also see
- Laravel Documentation - Official Laravel 11.x documentation
- Laravel Bootcamp - Interactive Laravel tutorial
- Laracasts - Laravel video tutorials
- Laravel News - Laravel community news and articles
- Laravel API Documentation - Complete API reference