Home  ›  Web Development  ›  Laravel

WEB DEVELOPMENT / LARAVEL

Laravel: Complete Guide to PHP Framework, MVC, APIs, Eloquent & Web Development

Learn Laravel from PHP framework fundamentals and MVC architecture to routing, controllers, Blade, Eloquent, databases, APIs, authentication, security, testing, performance and deployment.

Every concept comes with a short, copyable example using current Laravel patterns and secure defaults.

ILLUSTRATIVE LARAVEL ARCHITECTURE
CLIENTBrowser or React frontend
HTTPRequest arrives at the web server
ROUTEURL matched to a controller action
MIDDLEWAREAuth, CSRF, rate limiting
CONTROLLERCoordinates the work
SERVICEBusiness logic, where it earns its place
MODELEloquent queries the database
DATABASERows returned
RESPONSEBlade view or JSON
Illustrative Laravel application architecture showing routes controllers models and database

THE BASICS

What Is Laravel?

Laravel is a PHP web application framework. It provides structure and conventions for building web applications and backend systems — routing, request handling, database access, validation, authentication scaffolding, queues, caching, testing tools and a defined place for everything to live.

Laravel is not a programming language. It’s written in PHP, and Laravel applications are PHP applications. Everything you know about PHP classes, namespaces, interfaces, traits, type declarations and Composer applies directly — Laravel adds structure on top rather than replacing the language beneath. See our PHP guide for those foundations.

It’s also not frontend-only or backend-only in any narrow sense. Laravel can serve complete server-rendered pages through Blade, expose JSON APIs for a React or mobile frontend, or do both in the same application.

The value is mostly in what you don’t have to build: routing, an ORM, validation, authentication scaffolding, queue handling, caching, a testing setup and a migration system all come ready. The cost is learning its conventions and accepting a certain way of organising code. For a three-page site that’s overhead with no return; for an application with users, permissions, business rules and data relationships, it saves a great deal of work. Laravel is a good fit for many projects, not for every project.

Laravel is suited to

WHERE LARAVEL FITS

Laravel vs PHP, WordPress and React

Laravel and PHP

PHP is the language; Laravel is a framework written in it. Laravel gives you routing, an ORM, validation and conventions — PHP gives you the syntax, classes, types and standard library those tools are built from. Laravel does not replace PHP knowledge; it depends on it.

Laravel and React

They solve different problems and are not alternatives. React builds the interface in the browser; Laravel handles routing, data, business rules and authorisation on the server. A common architecture has React calling a Laravel API over HTTP and rendering the JSON it returns.

Laravel and WordPress are both PHP, and both build websites, but they start from opposite ends. WordPress gives you a working content system to extend; Laravel gives you a framework to build with. Neither is better — the right choice depends on whether your project is mostly content or mostly custom logic. A magazine site in Laravel means rebuilding what WordPress already does; a multi-tenant SaaS in WordPress means fighting a content model that wasn’t designed for it. See our CMS guides.

Aspect
WordPress
Laravel
What it is
WordPressA PHP content management system
LaravelA PHP application framework
Primary purpose
WordPressPublishing and managing content
LaravelBuilding custom application logic
Out of the box
WordPressAdmin interface, posts, media, users
LaravelStructure and tools; you build the features
Extending it
WordPressThemes and plugins
LaravelYour own code, plus Composer packages
Data model
WordPressPosts, meta and taxonomies
LaravelWhatever schema your application needs
Best suited to
WordPressContent sites, blogs, brochure sites
LaravelCustom business logic, APIs, dashboards
Learning curve
WordPressLower for content, steeper for deep customisation
LaravelSteeper initially, more predictable at scale
WordPress compared with Laravel for web projects

ARCHITECTURE

How Laravel Works: The Request Lifecycle and MVC

Every Laravel request follows the same path. It enters through a single public entry point, the framework bootstraps, middleware runs, the router matches the URL to a handler, a controller coordinates the work, models read and write data, and a response goes back — rendered HTML from Blade, or JSON for an API client.

MVC is the organising idea: models own data and business rules, controllers handle requests, views handle presentation. It’s a useful mental model and Laravel’s directory structure reflects it.

Real applications outgrow the three-box diagram though. Most non-trivial Laravel projects also have form requests for validation, services or action classes for business logic, policies for authorisation, jobs for background work, events and listeners for decoupling, and API resources for shaping JSON. MVC describes the skeleton; those layers are where the application actually lives. Not every project needs all of them, and adding layers before you need them is its own mistake.

LARAVEL MVC
MODELData and business rules, usually Eloquent models
CONTROLLERReceives the request, coordinates, returns a response
VIEWBlade templates producing HTML, or JSON for APIs
Laravel MVC architecture connecting models controllers and views
REQUESTHTTP request enters public/index.php
BOOTSTRAPFramework and service providers load
MIDDLEWAREGlobal and route middleware run
ROUTEURL and method matched to an action
CONTROLLERValidates, coordinates, delegates
MODELEloquent reads or writes the database
VIEW / JSONBlade renders HTML, or a resource returns JSON
RESPONSESent back through middleware to the client
Laravel request lifecycle from HTTP request through middleware, routing, controller and model to the response
 Laravel — routingEXAMPLE
<?php

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

// Basic routes by HTTP verb
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::put('/posts/{post}', [PostController::class, 'update']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);

// Route parameters
Route::get('/posts/{id}', function (string $id) {
    return $id;
});

// Route model binding — Laravel resolves the model for you
Route::get('/posts/{post}', function (Post $post) {
    return view('posts.show', ['post' => $post]);
});

// Named routes avoid hard-coded URLs
Route::get('/dashboard', [DashboardController::class, 'index'])
    ->name('dashboard');

// redirect()->route('dashboard');
// route('dashboard') inside Blade

// Groups share middleware and prefixes
Route::middleware(['auth'])
    ->prefix('admin')
    ->name('admin.')
    ->group(function () {
        Route::get('/users', [UserController::class, 'index'])->name('users');
    });

// Resource routes create the conventional CRUD set
Route::resource('products', ProductController::class);

ROUTING

Laravel Routing

Routes map an HTTP method and URL to something that handles it. They live in routes/web.php for browser-facing pages and routes/api.php for API endpoints, which get different middleware — sessions and CSRF for web, stateless handling for API.

Route parameters in braces capture parts of the URL. Route model binding takes that further: type-hint a model and Laravel fetches the matching record automatically, returning a 404 if it doesn’t exist. That removes a lookup and a not-found check from every controller method.

Named routes let you refer to a route by name rather than URL, so changing a path doesn’t mean hunting through views and redirects. Route groups apply shared middleware, URL prefixes and name prefixes to several routes at once — the usual way to protect an admin area.

Route::resource() generates the conventional CRUD routes in one line. Convenient, though worth knowing what it creates: php artisan route:list shows every registered route, and it’s the first command to reach for when a URL isn’t behaving.

HANDLING REQUESTS

Controllers, Middleware, Requests and Responses

Controllers receive requests and return responses. The healthiest ones are thin: validate, delegate, respond. A controller method that runs twenty lines of business logic, builds a query and formats output is doing three jobs, and it’ll be duplicated the moment a second entry point needs the same behaviour.

Middleware runs before or after the request reaches your controller, forming a pipeline. Authentication, CSRF verification, rate limiting, logging and redirecting unverified users are all middleware. Because it’s applied per route or per group, cross-cutting concerns stay out of controllers entirely.

The request object gives access to query parameters, form data, JSON input, headers and uploaded files. Use $request->input() or validated() rather than touching superglobals.

Responses can be a Blade view, a redirect, JSON, a file download or a bare status code. For APIs, returning the right HTTP status matters as much as the body — a 200 with an error message inside tells automated clients everything worked.

 Laravel — a controllerEXAMPLE
<?php

namespace App\Http\Controllers;

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

class PostController extends Controller
{
    public function index(Request $request)
    {
        $posts = Post::with('author')
            ->latest()
            ->paginate(20);

        return view('posts.index', ['posts' => $posts]);
    }

    public function show(Post $post)   // route model binding
    {
        return view('posts.show', ['post' => $post]);
    }

    public function store(StorePostRequest $request)
    {
        // Validation already ran in the form request
        $post = Post::create($request->validated());

        return redirect()
            ->route('posts.show', $post)
            ->with('status', 'Post created.');
    }

    // API-style response
    public function apiIndex()
    {
        return response()->json(Post::paginate(20));
    }
}
 Blade — templates, layouts and componentsEXAMPLE
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>@yield('title', 'My Application')</title>
</head>
<body>
    @include('partials.header')

    <main>
        @yield('content')
    </main>
</body>
</html>

{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')

@section('title', 'Posts')

@section('content')
    {{-- Escaped output — safe by default --}}
    <h1>{{ $title }}</h1>

    @if($posts->isNotEmpty())
        @foreach($posts as $post)
            <article>
                <h2>{{ $post->title }}</h2>
                <p>{{ Str::limit($post->body, 120) }}</p>
            </article>
        @endforeach

        {{ $posts->links() }}
    @else
        <p>No posts yet.</p>
    @endif

    {{-- A Blade component with a slot --}}
    <x-alert type="success">
        Your changes were saved.
    </x-alert>

    {{-- {!! $html !!} outputs raw HTML — only for trusted content --}}
@endsection

VIEWS

Blade Templates

Blade is Laravel’s templating engine. Templates live in resources/views, compile to plain PHP and are cached, so the directives cost nothing at runtime.

The most important thing about Blade is that {{ }} escapes output by default. Variables rendered that way can’t be interpreted as markup, which prevents most cross-site scripting without you remembering to do anything. The raw syntax {!! !!} opts out of that protection and should only ever be used with content you fully control or have sanitised.

Directives cover the usual logic: @if, @foreach, @forelse, @auth, @csrf. Layouts with @extends and @yield keep shared page structure in one file. Components go further, giving reusable pieces — buttons, alerts, cards, form fields — their own class and template, with slots for content. They’re the Blade equivalent of the component thinking React encourages, and worth using for anything that appears more than twice.

ELOQUENT

Eloquent ORM, Relationships and Queries

Eloquent is Laravel’s ORM. Each model maps to a database table, and instances map to rows, so you write PHP rather than SQL for most operations.

The $fillable property is a security control, not boilerplate. It lists which attributes may be set through mass assignment, so a request containing an unexpected is_admin field can’t quietly set it. Without it, passing request data straight into create() is genuinely dangerous.

Relationships describe how tables connect: hasOne for one-to-one, hasMany and belongsTo for one-to-many, belongsToMany for many-to-many through a pivot table, plus has-many-through and polymorphic relationships for more involved structures. Defined once on the model, they’re then available as properties and query methods.

The N+1 query problem is the one performance issue every Laravel developer meets. Loop over 50 posts accessing $post->author and you’ve run 51 queries: one for the posts, one per post for its author. Eager loading with with() fetches the related records in a second query instead. It’s invisible in development with ten rows and crippling in production with ten thousand — which is why query counting during development matters.

The query builder sits beneath Eloquent and can be used directly through the DB facade, which suits reporting queries and complex joins where models add nothing. Both use parameter binding, so neither concatenates your input into SQL.

 Laravel — Eloquent models and relationshipsEXAMPLE
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // Mass assignment protection — only these may be set in bulk
    protected $fillable = ['name', 'email'];

    protected $hidden = ['password', 'remember_token'];

    public function profile()
    {
        return $this->hasOne(Profile::class);
    }

    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function roles()
    {
        return $this->belongsToMany(Role::class);   // pivot table
    }

    // Reusable query scope
    public function scopeActive($query)
    {
        return $query->where('active', true);
    }
}

// Reading
$users = User::active()->orderBy('name')->get();
$user  = User::findOrFail($id);

// Writing
$user = User::create(['name' => 'Alex', 'email' => 'alex@example.com']);
$user->update(['name' => 'Sam']);
$user->delete();

// N+1 problem: 1 query + 1 per post
$posts = Post::all();
foreach ($posts as $post) { echo $post->author->name; }

// Eager loading: 2 queries total
$posts = Post::with('author')->get();

// Query builder for reporting work
$totals = DB::table('orders')
    ->select('status', DB::raw('count(*) as total'))
    ->groupBy('status')
    ->get();
 Laravel — migrations, factories and transactionsEXAMPLE
<?php

// database/migrations/xxxx_create_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        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('published')->default(false);
            $table->timestamps();

            // Index columns you filter or join on
            $table->index(['published', 'created_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

// Seeding with factories
User::factory()->count(10)->has(Post::factory()->count(3))->create();

// Transactions: all steps succeed, or none do
DB::transaction(function () use ($order, $items) {
    $order->save();

    foreach ($items as $item) {
        $item->product->decrement('stock');
    }
});

DATABASE

Migrations, Seeders, Factories and Transactions

Migrations define database structure in version-controlled PHP. Each has an up() that applies a change and a down() that reverses it, so schema history travels with your code and every environment can be brought to the same state with php artisan migrate. This is the main practical advantage over changing tables by hand — the structure is reproducible and reviewable.

Add indexes in migrations for columns you filter, sort or join on. They speed reads at the cost of storage and slightly slower writes, so index deliberately rather than everywhere.

Factories define how to generate realistic model data, and seeders use them to populate a database for development and testing. Together they mean a new developer can go from clone to working application with data in two commands, and tests can build exactly the scenario they need.

Transactions wrap several operations so they all succeed or all roll back. Anything touching multiple tables — creating an order while adjusting stock, transferring a value — needs one, or a failure halfway leaves inconsistent data.

Laravel supports several database systems including MySQL, MariaDB, PostgreSQL and SQLite, configured through environment variables. Which to use is a project decision.

VALIDATION

Validation, Forms and CSRF Protection

Validation in Laravel happens on the server, which is the only place it counts. Client-side checks improve the experience; they’re trivially bypassed.

For simple cases, $request->validate() in the controller is enough — it stops execution on failure, redirecting back with errors and old input for web requests, or returning a 422 with a structured error body for API requests. That dual behaviour is why the same validation works for both.

For anything substantial, a form request class moves the rules and authorisation into their own file. The controller then receives an already-validated request, and $request->validated() returns only the fields you declared — a natural guard against unexpected input reaching create().

CSRF protection is applied automatically to web routes. Blade’s @csrf directive adds the token to a form, and middleware verifies it, preventing another site from causing a logged-in user’s browser to submit a request they didn’t intend. Don’t disable it because a form is failing — that error usually means a missing token or an expired session. API routes use token or session authentication instead of CSRF tokens.

 Laravel — validation and form requestsEXAMPLE
<?php

// Inline validation in a controller
$validated = $request->validate([
    'name'     => ['required', 'string', 'max:255'],
    'email'    => ['required', 'email', 'unique:users,email'],
    'age'      => ['nullable', 'integer', 'min:18'],
    'document' => ['nullable', 'file', 'mimes:pdf', 'max:2048'],
]);

// A form request keeps rules out of the controller
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body'  => ['required', 'string'],
        ];
    }

    public function messages(): array
    {
        return ['title.required' => 'Please enter a title.'];
    }
}
 Blade — a protected formEXAMPLE
<form method="POST" action="{{ route('posts.store') }}">
    @csrf

    <label for="title">Title</label>
    <input id="title" name="title" value="{{ old('title') }}"
           @error('title') aria-invalid="true" @enderror>

    @error('title')
        <p>{{ $message }}</p>
    @enderror

    <button type="submit">Save post</button>
</form>
ILLUSTRATIVE AUTHENTICATION FLOW
LOGINCredentials submitted over HTTPS
VALIDATEInput checked, CSRF verified
VERIFYHashed password compared
SESSIONSession started, or a token issued for APIs
REQUESTAuth middleware confirms identity
AUTHORISEGate or policy checks permission
RESOURCEProtected data returned
Laravel authentication flow between frontend API and backend
 Laravel — authorisation with gates and policiesEXAMPLE
<?php

// A gate: a simple ability check
Gate::define('view-reports', function (User $user) {
    return $user->role === 'admin';
});

if (Gate::allows('view-reports')) { /* ... */ }

// A policy: authorisation organised around a model
namespace App\Policies;

class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->isAdmin();
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->isAdmin();
    }
}

// Enforced in the controller
public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);   // throws 403 if denied
    // ...
}

// In Blade — UX only, never the actual protection
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Edit</a>
@endcan

// Protecting routes
Route::middleware(['auth'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

AUTH

Authentication, Authorization and Sanctum

Authentication asks who someone is; authorisation asks what they may do. Conflating them is the most consequential mistake in this area — being logged in does not entitle a user to edit post 42.

Laravel handles authentication through guards and providers, with several official starter packages offering login, registration, password reset and verification. None is mandatory; you can build your own on the underlying pieces. Passwords are hashed automatically — never store them in plaintext.

For authorisation, gates suit standalone ability checks, while policies organise the rules for a particular model in one class. Both are used through authorize() in controllers, the can middleware on routes, and @can in Blade.

That Blade directive deserves a warning: hiding a button is not authorisation. It improves the interface for people who shouldn’t see the option; it stops nobody from calling the route directly. Enforce permission in the controller or middleware on every protected action.

Sanctum provides authentication for APIs and single-page applications — API tokens with abilities for mobile and third-party clients, or cookie-based session authentication for a first-party SPA on the same domain. It’s one option among several depending on architecture, not a requirement.

APIS

Building REST APIs With Laravel

Laravel is widely used as an API backend. API routes live in routes/api.php, return JSON, and use token or Sanctum authentication rather than session cookies and CSRF tokens.

API resources are the piece people skip and later regret. Returning a model directly exposes your database columns as your public contract, so a schema change breaks every client. A resource class defines exactly which fields appear and how they’re named, keeping the API stable while the database evolves — and keeping internal fields out of the response.

Status codes carry meaning: 200 for success, 201 for created, 401 for unauthenticated, 403 for forbidden, 404 for missing, 422 for validation failures and 500 for server errors. Laravel returns 422 with a structured errors object automatically when validation fails on an API request.

Pagination should be the default for collections — paginate() returns data alongside pagination metadata. Add rate limiting with throttle middleware on public and authentication endpoints, serve over HTTPS, validate everything, authorise every action, and make sure detailed exceptions never reach clients in production. More on API design in our website APIs guide.

Aspect
Web routes
API routes
Location
Web routesroutes/web.php
API routesroutes/api.php
Responses
Web routesBlade views and redirects
API routesJSON
State
Web routesSession cookies
API routesStateless tokens, or Sanctum SPA sessions
CSRF
Web routesToken verification applied
API routesNot applicable; token auth instead
Clients
Web routesBrowsers
API routesReact apps, mobile apps, external services
Errors
Web routesRedirect back with errors
API routesStatus code plus a structured error body
Laravel web routes compared with API routes
ILLUSTRATIVE API DATA FLOW
CLIENTReact app or mobile application
HTTPRequest with authentication credentials
MIDDLEWAREAuth, throttling, CORS
CONTROLLERForm request validates the input
POLICYAuthorisation checked for this action
MODELEloquent query, eager loaded
RESOURCEJSON shaped and returned with a status code
Laravel API request and response flow
 Laravel — an API controller and resourceEXAMPLE
<?php

// An API resource shapes the JSON contract
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id'        => $this->id,
            'title'     => $this->title,
            'author'    => new UserResource($this->whenLoaded('author')),
            'published' => (bool) $this->published,
            'createdAt' => $this->created_at->toIso8601String(),
        ];
    }
}

// The controller
class PostApiController extends Controller
{
    public function index()
    {
        return PostResource::collection(
            Post::with('author')->paginate(20)
        );
    }

    public function store(StorePostRequest $request)
    {
        $post = Post::create($request->validated());

        return (new PostResource($post))
            ->response()
            ->setStatusCode(201);
    }
}

// Protected, rate-limited API routes
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
    Route::apiResource('posts', PostApiController::class);
});

INTEGRATION

Laravel With React, JavaScript and WordPress

The pattern for any JavaScript frontend is the same: Laravel exposes JSON endpoints, the frontend calls them over HTTP, and everything that must be trusted — validation, authorisation, business rules, database access — stays on the Laravel side. A React app hiding an admin button changes nothing about what the API will accept.

For a first-party React app on the same domain, Sanctum’s SPA authentication uses cookies and existing session infrastructure. For mobile apps or third-party clients, API tokens fit better. Laravel also supports server-rendered pages with islands of JavaScript interactivity — not every project needs a separate frontend application, and that choice has real consequences for complexity and SEO.

WordPress and Laravel can coexist. A common arrangement has WordPress handling editorial content and exposing it through its REST API, with a Laravel application consuming that content alongside its own data. It’s an architecture worth choosing deliberately rather than by default — two systems means two deployments, two sets of updates and two places for things to break.

 JavaScript — calling a Laravel APIEXAMPLE
// Fetching from a Laravel API endpoint
async function loadPosts() {
  const response = await fetch("/api/posts", {
    headers: {
      "Accept": "application/json",
      "Authorization": `Bearer ${token}`,
    },
  });

  // fetch does not reject on 4xx or 5xx
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  const { data, meta } = await response.json();
  return { posts: data, pagination: meta };
}

// Handling Laravel validation errors (422)
async function createPost(payload) {
  const response = await fetch("/api/posts", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Accept": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (response.status === 422) {
    const { errors } = await response.json();
    return { errors };   // { title: ["Please enter a title."] }
  }

  return { post: await response.json() };
}
 Laravel — services, dependency injection and configEXAMPLE
<?php

// A service class holds business logic
namespace App\Services;

class OrderService
{
    public function __construct(
        private PaymentGateway $gateway,
        private InventoryService $inventory,
    ) {}

    public function place(User $user, array $items): Order
    {
        return DB::transaction(function () use ($user, $items) {
            $order = $user->orders()->create(['status' => 'pending']);

            $this->inventory->reserve($items);
            $this->gateway->charge($order->total());

            return $order;
        });
    }
}

// The container injects dependencies automatically
class OrderController extends Controller
{
    public function __construct(private OrderService $orders) {}

    public function store(PlaceOrderRequest $request)
    {
        $order = $this->orders->place($request->user(), $request->validated());

        return redirect()->route('orders.show', $order);
    }
}

// Config reads from the environment — never hard-code secrets
// config/services.php
return [
    'gateway' => [
        'key' => env('GATEWAY_KEY'),
    ],
];

// Read through config(), not env(), outside config files
$key = config('services.gateway.key');

STRUCTURE

Services, Dependency Injection and Configuration

When controllers grow past coordinating, move the work into a service or action class. The test is reuse and testability: logic needed by both a web controller and an API controller belongs somewhere both can call. Don’t create a service layer for every model on principle — over-layering a simple CRUD application adds indirection and nothing else. The same applies to repository patterns, which Laravel does not require and which often just wrap Eloquent in a thinner Eloquent.

Dependency injection means a class declares what it needs in its constructor rather than creating it. Laravel’s service container resolves those type-hinted dependencies automatically, including into controller methods. The practical payoff is testing: you can substitute a fake payment gateway without touching the class being tested.

Service providers are where bindings and bootstrapping are registered — telling the container which implementation to use for an interface, registering event listeners, publishing package configuration.

Configuration lives in config/ and reads from the .env file. Two rules matter: .env must never be committed to version control, and outside config files you should read values through config() rather than env(), because cached configuration in production makes env() return null.

BACKGROUND WORK

Queues, Jobs, Events, Mail and Scheduling

Slow work doesn’t belong in a web request. Queues let you dispatch a job and respond immediately, while a background worker does the actual work — sending email, processing images, generating reports, importing data, calling slow third-party APIs.

Queues need a driver (database, Redis and others are supported) and a running worker process, which is a real deployment consideration: no worker means jobs pile up silently. Laravel handles retries and records failures so they can be inspected and retried rather than vanishing.

Events and listeners decouple related behaviour. An OrderPlaced event can have listeners that send a confirmation, update analytics and notify the warehouse — each in its own class, each addable without touching the code that placed the order. Overuse makes flow hard to follow, so reserve it for genuinely independent reactions.

Notifications send messages across channels, with mail and database available by default and others through additional packages. Mailables define emails as classes with Blade templates, and queuing them is usually right, since SMTP is slow.

Task scheduling defines recurring work in PHP — cleanups, reports, synchronisation — run by a single cron entry calling Laravel’s scheduler, rather than a growing list of crontab lines.

ILLUSTRATIVE QUEUE WORKFLOW
REQUESTUser submits an order
DISPATCHJob pushed to the queue
RESPONSEUser gets an immediate reply
QUEUEJob waits in Redis or the database
WORKERBackground process picks it up
TASKEmail sent, image processed, report built
RETRYFailed jobs retried, then logged as failed
Laravel queue workflow from dispatching a job to a background worker
 Laravel — jobs, events and schedulingEXAMPLE
<?php

// A queued job
namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;

class SendWelcomeEmail implements ShouldQueue
{
    public $tries = 3;

    public function __construct(public User $user) {}

    public function handle(): void
    {
        Mail::to($this->user)->send(new WelcomeMail($this->user));
    }
}

SendWelcomeEmail::dispatch($user);
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));

// An event with independent listeners
OrderPlaced::dispatch($order);
// Listeners: SendConfirmation, UpdateAnalytics, NotifyWarehouse

// Scheduled tasks (routes/console.php or the scheduler)
Schedule::command('reports:daily')->dailyAt('02:00');
Schedule::call(fn () => Cache::forget('stats'))->hourly();

// Running a worker
// php artisan queue:work --tries=3
 Laravel — caching and file storageEXAMPLE
<?php

// Cache an expensive result
$stats = Cache::remember('dashboard.stats', now()->addMinutes(15), function () {
    return [
        'orders'  => Order::whereMonth('created_at', now()->month)->count(),
        'revenue' => Order::sum('total'),
    ];
});

Cache::forget('dashboard.stats');   // invalidate when data changes

// Storing an uploaded file safely
$request->validate([
    'document' => ['required', 'file', 'mimes:pdf,jpg,png', 'max:2048'],
]);

// store() generates a safe random filename
$path = $request->file('document')->store('documents');

// Public files, served through the storage link
$publicPath = $request->file('avatar')->store('avatars', 'public');
$url = Storage::disk('public')->url($publicPath);

// Private files: serve through a controller with an authorisation check
public function download(Document $document)
{
    $this->authorize('view', $document);

    return Storage::download($document->path);
}

CACHING & FILES

Caching, Redis, Filesystem and Uploads

Caching stores the result of expensive work so it isn’t repeated. Cache::remember() is the common pattern: return the cached value if present, otherwise run the callback and store it. The genuinely hard part isn’t caching — it’s invalidation. A cached dashboard showing figures from twenty minutes ago is worse than a slightly slower accurate one, so decide when each cached value must be cleared.

Laravel supports several cache drivers. Redis is commonly used for caching, sessions and queues in production because it’s fast and shared across servers, but it isn’t required — file and database drivers work fine for smaller applications.

The filesystem abstraction gives one API across local disks and cloud storage, so moving from local files to object storage is a configuration change rather than a rewrite.

Uploads need layered checks: validate the type and size through validation rules, let store() generate the filename rather than trusting the submitted one, and keep files that need permission checks out of the public disk — serve those through a controller that authorises the request first. Files in the public disk are readable by anyone with the URL.

TOOLING

Artisan, Composer and Project Structure

Artisan is Laravel’s command-line interface. It generates classes, runs migrations, manages caches, starts queue workers and lists routes, and you can write your own commands for maintenance tasks.

Composer manages PHP dependencies. composer install installs exactly what’s in the lock file — which is what you run on a server. composer update resolves new versions and rewrites the lock file, which belongs in development, tested, and committed. Running it directly on production is a good way to deploy an untested dependency upgrade.

The project structure follows conventions worth knowing: app/ holds application code with Http/ for controllers, requests and middleware and Models/ for Eloquent models; routes/ separates web and API routes; resources/views/ holds Blade templates; database/ holds migrations, factories and seeders; config/ holds configuration; storage/ holds logs, cache and uploads; tests/ holds tests; and public/ is the only directory the web server should expose.

That last point matters for deployment: the document root points at public/, so everything else — including .env — sits outside the web-accessible path.

project/
├── app/
│   ├── Http/      controllers, requests, middleware
│   ├── Models/
│   ├── Services/
│   ├── Jobs/
│   └── Providers/
├── routes/     web.php, api.php, console.php
├── resources/  views, css, js
├── database/   migrations, factories, seeders
├── config/
├── storage/    logs, cache, uploads
├── tests/
├── public/     the only web-accessible directory
├── .env         never committed
└── composer.json
Example Laravel project structure showing app, routes, resources, database and public directories
 Artisan and Composer — useful commandsEXAMPLE
# Inspecting the application
php artisan route:list
php artisan about
php artisan migrate:status

# Generating classes
php artisan make:controller PostController --resource
php artisan make:model Product -m
php artisan make:request StorePostRequest
php artisan make:policy PostPolicy --model=Post
php artisan make:job SendWelcomeEmail

# Database
php artisan migrate
php artisan migrate:rollback
php artisan db:seed

# Clearing caches during development
php artisan config:clear
php artisan cache:clear
php artisan view:clear

# Queue worker
php artisan queue:work

# Dependencies
composer install          # use this on servers
composer update           # in development, then commit the lock file
 Laravel — feature, database and API testsEXAMPLE
<?php

namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase
{
    use RefreshDatabase;   // fresh database per test

    public function test_guests_cannot_create_posts(): void
    {
        $response = $this->post('/posts', ['title' => 'Test']);

        $response->assertRedirect('/login');
    }

    public function test_a_user_can_create_a_post(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->post('/posts', [
            'title' => 'My first post',
            'body'  => 'Some content.',
        ]);

        $response->assertRedirect();
        $this->assertDatabaseHas('posts', ['title' => 'My first post']);
    }

    public function test_validation_errors_are_returned(): void
    {
        $user = User::factory()->create();

        $this->actingAs($user)
            ->postJson('/api/posts', [])
            ->assertStatus(422)
            ->assertJsonValidationErrors(['title', 'body']);
    }

    public function test_users_cannot_edit_others_posts(): void
    {
        $post = Post::factory()->create();

        $this->actingAs(User::factory()->create())
            ->put("/posts/{$post->id}", ['title' => 'Hijacked'])
            ->assertForbidden();
    }
}

TESTING

Testing Laravel Applications

Laravel ships with a testing setup, and the framework’s test helpers are a large part of why testing here feels worthwhile rather than tedious.

Feature tests exercise a complete request: send it, assert on the status, redirect, session state or JSON. They give the best return for the effort because they test what users actually do rather than internal wiring. Unit tests cover individual classes in isolation — useful for calculation-heavy services.

The RefreshDatabase trait gives each test a clean database, and factories build exactly the records the scenario needs. Assertions like assertDatabaseHas() check the result at the data level, and actingAs() authenticates as a given user in a line.

If you test nothing else, test authorisation. The tests worth writing first are the negative ones: a guest can’t reach a protected route, a user can’t edit someone else’s record, an invalid payload returns 422. Those are the failures that matter most and the ones manual testing consistently misses, because you’re logged in as an admin while you work.

SECURITY

Laravel Security

Laravel provides good defaults, and the crucial caveat is that defaults are not immunity. A framework can prevent categories of mistake; it can’t prevent you from writing insecure code.

SQL injection is largely handled because Eloquent and the query builder use parameter binding. Raw expressions with user input concatenated in bypass that protection entirely — if you need raw SQL, bind the parameters.

Mass assignment protection comes from $fillable or $guarded. Without it, a request containing extra fields can set columns you never intended, which is how a registration form quietly grants admin rights.

XSS is mitigated by Blade escaping output in {{ }}. The raw syntax opts out; use it only with content you control or have sanitised with a maintained library.

Passwords are hashed by Laravel’s hashing services. Never store them in plaintext, and never use MD5 or SHA-1 for the purpose.

Rate limiting through throttle middleware protects login forms, password resets and public API endpoints from brute force and abuse. And the most common production mistake: debug mode left enabled, which exposes file paths, configuration and sometimes credentials on any error page.

Aspect
CSRF
XSS
What it is
CSRFTricks a logged-in browser into sending an unwanted request
XSSInjects content that the browser executes as script
Target
CSRFThe user's authenticated session
XSSThe user's browser and page content
Laravel's default
CSRFCSRF middleware on web routes, @csrf in forms
XSSBlade escapes output with {{ }}
Where it breaks
CSRFDisabling the middleware, or state changes via GET
XSSUsing {!! !!} with untrusted content
Also helps
CSRFSameSite cookies, no state changes on GET
XSSSanitising any HTML you must render
CSRF compared with XSS and how Laravel addresses each

Laravel security checklist

 Laravel security checklistEXAMPLE
[ ] Validate all input server-side
[ ] Use form requests for substantial rules
[ ] Authorise every protected action with gates or policies
[ ] Set $fillable or $guarded on every model
[ ] Keep .env out of version control
[ ] Disable debug mode in production
[ ] Serve everything over HTTPS
[ ] Apply rate limiting to auth and API endpoints
[ ] Validate file uploads and store them safely
[ ] Never expose stack traces to users
[ ] Keep PHP, Laravel and packages updated
[ ] Review third-party dependencies
[ ] Use database transactions for related writes
[ ] Log security events without logging secrets
 Laravel — performance patternsEXAMPLE
<?php

// Eager load to avoid N+1 queries
$posts = Post::with(['author', 'tags'])->paginate(20);

// Select only the columns you need
$users = User::select(['id', 'name', 'email'])->get();

// Paginate rather than loading everything
$orders = Order::latest()->paginate(50);

// Chunk large datasets to keep memory flat
Order::where('status', 'pending')->chunkById(500, function ($orders) {
    foreach ($orders as $order) {
        ProcessOrder::dispatch($order);
    }
});

// Cache expensive aggregates
$revenue = Cache::remember('revenue.month', now()->addHour(), function () {
    return Order::whereMonth('created_at', now()->month)->sum('total');
});

// Move slow work off the request
GenerateMonthlyReport::dispatch($month);

# Production optimisation commands
# php artisan config:cache
# php artisan route:cache
# php artisan view:cache

PERFORMANCE

Laravel Performance

In most Laravel applications the bottleneck is the database, and specifically the number of queries rather than their individual speed. Measure before optimising — a query-logging tool during development tells you more than any amount of guessing.

Eager loading is the highest-value fix, since N+1 problems appear naturally as soon as you loop over related data. After that: index the columns you filter and join on, select only the columns you need, paginate collections rather than loading everything, and chunk large datasets so memory stays flat.

Caching helps for expensive aggregates and repeated reads, provided you’ve thought about invalidation. Queues improve perceived performance by moving slow work out of the request entirely — often a bigger win than optimising the work itself.

In production, config:cache, route:cache and view:cache reduce per-request work, and OPcache should be enabled at the PHP level. Run a supported PHP version. Backend response time is one input to overall page speed alongside assets, images and front-end work — see website speed for the full picture.

DEPLOYMENT

Deploying Laravel

A Laravel application needs a PHP runtime of a supported version with the required extensions, a web server such as Nginx or Apache with the document root pointing at public/, a database, Composer available during deployment, writable storage, and HTTPS. If you use queues or scheduled tasks, you also need a supervised worker process and a cron entry — both easy to forget, and both silent when missing.

A typical deployment workflow runs: commit to version control, run tests, install dependencies from the lock file, set environment configuration, run migrations, rebuild caches, restart workers, then monitor. Automating it through CI/CD makes deployments repeatable and reduces the chance of skipping a step. Which CI provider you use is a preference, not a technical requirement.

Two things belong in version control discipline: never commit .env or any production credentials, and commit the Composer lock file so servers install exactly what you tested. Hosting requirements are covered further in our hosting and domains guides.

Production checklist

 

ILLUSTRATIVE PRODUCTION ARCHITECTURE
DOMAINDNS points to the server
HTTPSTLS terminated at the web server
WEB SERVERNginx or Apache, document root at public/
PHP-FPMRuns the Laravel application
LARAVELApplication code, config cached
DATABASEMySQL, PostgreSQL or another supported system
WORKERSQueue workers and the scheduler, kept running
STORAGEUploads, logs and cache, with correct permissions
Laravel deployment architecture connecting web server PHP application and database
DEVELOPWrite code and tests
COMMITPush to version control
TESTCI runs the test suite
INSTALLcomposer install from the lock file
CONFIGUREEnvironment variables set
MIGRATEDatabase migrations run
OPTIMISEConfig, route and view caches built
RESTARTQueue workers restarted
MONITORLogs and errors watched
Laravel deployment workflow from development through testing and migration to monitoring

APPLICATIONS

Laravel, SEO and What You Can Build

Laravel does not make a website SEO-friendly automatically, and nothing about the framework affects rankings by itself. What it does give you is control: clean URLs through routing, server-rendered HTML through Blade, per-page titles and meta descriptions, canonical URLs, real internal links and structured data where appropriate.

Server-rendered Blade output does have one structural advantage worth stating plainly: the content is in the initial HTML response, so it doesn’t depend on a crawler executing JavaScript. That removes a category of problem rather than guaranteeing anything — content quality, relevance, performance and accessibility still determine results. See our website SEO guides.

Laravel applications commonly serve both web and API routes: Blade pages for public content where server rendering helps, and JSON endpoints for a React dashboard or mobile app behind a login. That’s often the most practical architecture — you get crawlable content where it matters and a proper API where it’s needed, from one codebase and one set of business rules.

REQUESTBrowser or crawler requests a URL
ROUTELaravel matches the route
CONTROLLERData fetched and prepared
BLADEHTML rendered on the server
RESPONSEComplete HTML sent in the first response
CRAWLERContent available without running JavaScript
Server-rendered Laravel request flow producing HTML available to browsers and crawlers

Business Applications

Custom database-driven systems built around specific business processes.

Admin Dashboards

Internal tools with roles, permissions and reporting.

REST APIs

Backends for React frontends, mobile apps and third-party integrations.

E-Commerce Backends

Products, carts, orders, payments and fulfilment workflows.

Custom CMS

Content systems shaped around a specific editorial model.

SaaS Applications

Multi-tenant platforms with subscriptions and per-account data.

Booking Systems

Availability, scheduling, reminders and confirmations.

CRM Systems

Customers, activity history, permissions and reporting.

Learning Platforms

Courses, enrolment, progress tracking and content delivery.

PRACTICE

Laravel Best Practices and Common Mistakes

Validate Server-Side

Form requests for anything beyond trivial rules.

Authorise Every Action

Policies and gates, enforced in the backend.

Keep Controllers Thin

Validate, delegate, respond.

Use Eloquent Deliberately

Understand what each query actually runs.

Eager Load Relations

Load related data intentionally.

Protect Secrets

Environment variables, never in the repository.

Queue Slow Work

Keep the request fast.

Test What Matters

Auth, permissions and money paths first.

Use HTTPS

Everywhere, enforced.

Monitor Production

Logs, errors and slow queries.

Common Laravel mistakes to avoid

Fat Controllers

Business logic, queries and formatting all in one method.

Skipping Validation

Trusting request data because the form looked fine.

Missing Authorisation

Checking that someone is logged in, not what they may do.

Unnecessary Raw Queries

Reaching for raw SQL where Eloquent would do, losing binding safety.

Ignoring N+1 Queries

Fine with ten rows, crippling with ten thousand.

Missing Indexes

Queries scanning whole tables as data grows.

Hard-Coded Secrets

Credentials in code instead of environment variables.

Committing .env

Production credentials in the repository history.

Debug Mode in Production

Exposing paths, config and sometimes credentials.

Trusting Frontend Validation

Client-side checks treated as protection.

Disabling CSRF

Turning off middleware to make a form submit.

Unescaped Blade Output

Raw syntax used with untrusted content.

Careless File Uploads

No type or size validation, public storage for private files.

No API Error Handling

Errors returned as 200, or raw exceptions leaked.

Over-Layering

Services and repositories for a simple CRUD screen.

No Transactions

Related writes that can fail halfway.

Slow Work in Requests

Emails and reports blocking the response instead of queued.

Untested Critical Paths

Auth and payments verified only by clicking around.

DEBUGGING

Debugging Laravel Applications

Laravel’s logs in storage/logs are the first place to look — exceptions, stack traces and anything you’ve logged yourself. Log useful context such as the user id and the operation, but never passwords, tokens or personal data.

php artisan route:list answers most routing confusion by showing exactly what’s registered, with which middleware. php artisan about shows the environment and cache state, which frequently explains why a change appears to have no effect — a cached config or route file in development is a classic false alarm, cleared with config:clear and route:clear.

For database problems, look at the queries themselves: how many ran, in what order, with what bindings. Query-logging and debugging packages are common in development and should not be installed in production.

For API problems, the response status is the fastest signal: 401 means authentication, 403 means authorisation, 422 means validation with the failing fields listed, and 500 means an exception you’ll find in the logs. In production, error detail belongs in logs and monitoring — never in the response.

01

Reproduce

Find the exact request that fails.

02

Check the Logs

storage/logs holds the exception and stack trace.

03

Inspect the Request

Method, route, payload, headers and authenticated user.

04

Check the Route

route:list confirms what actually matched.

05

Inspect the Queries

Which queries ran, how many, and with what bindings.

06

Check the Response

Status code and body, especially for APIs.

07

Clear Caches

Config and route caches can mask a change.

08

Fix and Test

Cover the case with a test before deploying.

 Laravel — debugging helpersEXAMPLE
# See every registered route and its middleware
php artisan route:list
php artisan route:list --path=api

# Environment and cache state
php artisan about
php artisan migrate:status

# Clear caches when changes seem to have no effect
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear

# Tail the log while reproducing the issue
tail -f storage/logs/laravel.log

<?php

// Log with context, never with secrets
Log::info('Order placed', ['order_id' => $order->id, 'user_id' => $user->id]);
Log::error('Payment failed', ['order_id' => $order->id]);

// Inspect the queries a block of code runs
DB::listen(fn ($query) => Log::debug($query->sql, $query->bindings));

REFERENCE

Laravel Cheat Sheet

A categorised quick reference for the features covered above. For authoritative documentation on any of them, use the official Laravel documentation at laravel.com/docs, and php.net for the language itself.

ROUTESRoute::get()Route::resource()->name()->middleware()
ARTISANmake:controllermake:model -mmigrateroute:list
CONTROLLERreturn view()return redirect()response()->json()
ELOQUENT::all()::find()::where()::create()->update()->delete()
RELATIONShasOne()hasMany()belongsTo()belongsToMany()
QUERIESwith()paginate()chunkById()DB::transaction()
MIGRATIONSSchema::create()$table->id()foreignId()->index()
VALIDATION$request->validate()FormRequestvalidated()
BLADE{{ $var }}@if@foreach@csrf@extends<x-component>
AUTHauth()->user()@canauthorize()middleware('auth')
QUEUESJob::dispatch()ShouldQueuequeue:work
CACHE & FILESCache::remember()Storage::disk()->store()
Categorised Laravel reference covering routes, Artisan, controllers, Eloquent, relationships, queries, migrations, validation, Blade, auth, queues and storage

INTERVIEW PREP

Common Laravel Interview Questions

Concise, technically accurate answers to the Laravel questions that come up most often in interviews and technical assessments.

LEARNING PATH

Laravel Learning Roadmap and Practice Projects

PHP and object-oriented PHP sit before Laravel deliberately. Laravel’s conventions make more sense when you understand classes, interfaces, namespaces and Composer — most “Laravel is confusing” frustration is actually PHP unfamiliarity in a framework costume.

HTML
CSS
JavaScript basics
PHP
Object-oriented PHP
Composer
Laravel basics
Routing
Controllers
Blade
Eloquent
Databases and migrations
Validation
Authentication
Authorization
APIs
Queues and jobs
Testing
Security
Performance
Deployment
Production projects

These are projects to build yourself rather than templates to download. Each introduces a specific set of problems, and each will raise questions — particularly around authorisation and data relationships — that reading alone never does.

Blog Application

Beginner — routes, controllers, Blade and Eloquent.

Contact Manager

Beginner — CRUD, validation and relationships.

Task Management App

Intermediate — authentication, policies and relationships.

REST API

Intermediate — resources, validation and Sanctum.

Inventory System

Intermediate — categories, stock, transactions and reporting.

E-Commerce Backend

Advanced — carts, orders, payments and business rules.

CRM

Advanced — roles, permissions, activity history and reports.

Laravel + React App

Advanced — API-driven frontend and backend integration.

SaaS Dashboard

Advanced — multi-tenancy, queues and subscription concepts.

REQUIREMENTWhat the feature must do
DATABASESchema and relationships designed
MIGRATIONSStructure written as code
ROUTESURLs and methods defined
CONTROLLERRequest handling
VALIDATIONForm requests and rules
LOGICBusiness rules, in services where warranted
ELOQUENTModels, relationships and queries
VIEW / APIBlade output or JSON resources
TESTSAuth, validation and happy path
SECURITYAuthorisation reviewed
DEPLOYMigrate, cache, restart workers
Laravel project workflow from requirement and database design through validation, logic, testing and deployment

NEXT TOPICS

Continue Learning Web Development

PHP

Learn the server-side programming language Laravel is built with.

React

Learn how a frontend UI library communicates with Laravel APIs.

JavaScript

Understand the browser-side language used alongside Laravel applications.

Website APIs

Learn how Laravel can expose and consume backend APIs.

HTML

Understand the markup delivered by server-rendered Laravel applications.

CSS

Learn how Laravel-generated interfaces are styled responsively.

CMS

Explore WordPress and other PHP-based content platforms.

Hosting & Domains

Understand the server environment Laravel applications run on.

FAQ

Laravel: Frequently Asked Questions

Short answers to the questions people ask most about Laravel and PHP framework development.

Build Modern Web Applications With Laravel

Learn how PHP, Laravel routing, MVC, Eloquent, APIs, authentication, security, testing and deployment come together to build modern web applications.