FNA Technology Header Logo
ServicesWorkAboutBlog
[ start a project ]
FNA Technology Footer Logo

Transforming your digital vision into reality. Software, AI, web & mobile — built for outcomes.

[email protected] footer link+91 8879510299 footer link
Share page:
MAIN
HomeOur ServicesProjectsCompany Blog
COMPANY
About UsContact UsLinkedIn
LEGAL
Privacy PolicyTerms & Conditions
© 2026 FNA TECHNOLOGY LLP — ALL RIGHTS RESERVEDIndia · UK · Middle East
Getting Started with Laravel: A Developer's Practical GuideA step-by-step developer's guide to the Laravel framework. Learn MVC architecture, database migrations, routing, and building secure REST APIs.Business owners, developers, CTOsLaravel framework tutorial, Laravel PHP development, Laravel Eloquent ORM, Laravel service container, Laravel queue system, Laravel authentication, PHP web development 2026FNA Technology
Web Development

Getting Started with Laravel: A Developer's Practical Guide

May 4, 2026
6 min read
FNA Technology
Laravel PHP framework code editor showing Eloquent ORM and routing structure

The short version: Laravel's elegance comes from understanding the service container, Eloquent's query builder, and the queue architecture — not from memorizing artisan commands. This guide covers the pieces that matter for production apps, including where Laravel's "magic" becomes a maintenance problem.

Laravel is PHP's most popular framework, and it earns that position. But most Laravel tutorials teach you the surface — routes, controllers, Blade templates — and leave the pieces that matter for production applications unexplained. This guide assumes you've seen a Route::get() before and covers what you actually need to build something that holds up.

The service container: what's actually happening behind the facades

Laravel's facades look like static method calls but aren't. Cache::get('key') resolves the cache manager from the service container and calls get() on it. This matters for two reasons: testing and understanding what you can replace.

The service container is a dependency injection (DI) system. When Laravel resolves a class from the container, it inspects the constructor type hints and resolves each dependency automatically.

Code
// Without DI — tightly coupled, hard to test
class OrderController extends Controller
{
    public function store(Request $request)
    {
        $mailer = new OrderMailer(new SmtpTransport(...)); // manually wired
        $mailer->sendConfirmation($request->all());
    }
}

// With DI — container resolves OrderMailer automatically
class OrderController extends Controller
{
    public function __construct(private OrderMailer $mailer) {}

    public function store(Request $request)
    {
        $this->mailer->sendConfirmation($request->validated());
    }
}

In the second example, you can bind a fake OrderMailer in tests without touching the controller. That's why the container matters.

Binding a custom implementation:

Code
// AppServiceProvider.php
public function register(): void
{
    $this->app->bind(PaymentGateway::class, StripeGateway::class);

    // Or conditionally based on environment
    $this->app->bind(StorageDriver::class, function () {
        return app()->environment('testing')
            ? new FakeStorageDriver()
            : new S3StorageDriver(config('filesystems.s3'));
    });
}

Routing: beyond the basics

Laravel routes support grouping, middleware, naming, and resource conventions. The patterns that pay off most in larger applications:

Code
// routes/api.php
Route::prefix('v1')->middleware(['auth:sanctum', 'throttle:api'])->group(function () {

    Route::apiResource('orders', OrderController::class);
    // Generates: GET /v1/orders, POST /v1/orders, GET /v1/orders/{order},
    //            PUT /v1/orders/{order}, DELETE /v1/orders/{order}

    Route::post('orders/{order}/cancel', [OrderController::class, 'cancel'])
        ->name('orders.cancel');
});

Route model binding resolves the Order instance automatically from the URL parameter:

Code
// The {order} parameter resolves to an Order model automatically
public function show(Order $order): JsonResponse
{
    return response()->json($order->load('items', 'customer'));
}

Custom binding when you need to find by a different key:

Code
// RouteServiceProvider.php
Route::bind('order', function (string $value) {
    return Order::where('reference_number', $value)->firstOrFail();
});

Eloquent ORM: relationships and the N+1 problem

Eloquent is where most Laravel developers spend the most time and where the most common performance problems originate.

Defining relationships:

Code
class Order extends Model
{
    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }
}

The N+1 problem and how to find it:

Code
// N+1 — fires 1 query for orders + 1 per order for customer = N+1 total
$orders = Order::all();
foreach ($orders as $order) {
    echo $order->customer->name; // separate query per iteration
}

// Eager loading — fires 2 queries total regardless of collection size
$orders = Order::with('customer')->get();

// Multiple relationships
$orders = Order::with(['customer', 'items', 'items.product'])->get();

Install Laravel Debugbar in development (composer require barryvdh/laravel-debugbar --dev). It shows the exact SQL queries fired per request. You will find N+1 issues on day one.

Query scopes for reusable filters:

Code
class Order extends Model
{
    public function scopePending(Builder $query): Builder
    {
        return $query->where('status', 'pending');
    }

    public function scopeForCustomer(Builder $query, int $customerId): Builder
    {
        return $query->where('customer_id', $customerId);
    }
}

// Usage — reads like English
Order::pending()->forCustomer(42)->with('items')->latest()->get();

Queues: what to offload and how

Any operation over ~500ms that doesn't need to block the response belongs in a queue. The most common candidates: email, PDF generation, image processing, third-party API calls, and webhook delivery.

Code
// Create a job
class SendOrderConfirmation implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(private Order $order) {}

    public function handle(Mailer $mailer): void
    {
        $mailer->to($this->order->customer->email)
               ->send(new OrderConfirmationMail($this->order));
    }

    // Retry up to 3 times if the job fails
    public int $tries = 3;

    // Wait 60 seconds before retry
    public int $backoff = 60;
}

// Dispatch from a controller — returns immediately, job runs in background
SendOrderConfirmation::dispatch($order);

// Dispatch with delay
SendOrderConfirmation::dispatch($order)->delay(now()->addMinutes(5));

For production, use Redis as the queue driver — it's significantly faster than the database driver and doesn't lock tables under load. Run workers with Supervisor to keep them alive:

Code
; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=4

Authentication: Sanctum vs Passport

Most applications need Sanctum. It handles:

  • SPA authentication — cookie-based sessions for same-domain frontends
  • API tokens — simple opaque tokens for mobile apps and third-party scripts
Code
// Issue a token (e.g., for a mobile app login)
$token = $user->createToken('mobile-app', ['orders:read', 'orders:write']);
return ['token' => $token->plainTextToken];

// Protect routes
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn(Request $r) => $r->user());
});

// Check token abilities
if ($request->user()->tokenCan('orders:write')) {
    // proceed
}

Use Passport only when you're building an OAuth2 provider — authorization code flows, client credentials grants, or a public API where external developers authenticate their own applications. For anything else, Sanctum handles it with far less complexity.

Where Laravel's magic becomes a problem

Laravel is opinionated about hiding complexity. That's the source of its productivity advantage and its main maintenance liability.

Facades make tracing execution harder. Log::info('message') looks like a static call. It's actually resolving a LogManager from the container, which is binding to a Monolog\Logger, which is writing to a handler. When something goes wrong in production, this indirection makes debugging slower. Use app('log') or inject the LoggerInterface directly in performance-critical paths where you want the dependency chain visible.

Eloquent $fillable vs. $guarded. Setting protected $guarded = [] on a model (allowing all fields to be mass-assigned) is convenient until a user sends an unexpected field in a POST request and writes directly to a column you didn't intend. Always define explicit $fillable arrays on models that accept user input.

N+1 is not always obvious. Nested relationships accessed in Blade templates are the worst case — by the time you see the page render, Laravel has fired 40 queries and the Blade file looks clean. Enable Model::preventLazyLoading() in development and it will throw an exception every time you access an unloaded relationship. Painful to set up, extremely useful.

Code
// AppServiceProvider.php
public function boot(): void
{
    Model::preventLazyLoading(! app()->isProduction());
}

For teams building full-stack applications with Laravel on the backend and React or Next.js on the frontend, the Next.js modern web development guide covers how the two stacks connect via API resources and authentication headers.

Useful artisan commands for daily development

Code
# Generate a model with migration, factory, seeder, and resource controller
php artisan make:model Order -mfsr --api

# Run migrations on a fresh database with seeders
php artisan migrate:fresh --seed

# Start queue worker (development)
php artisan queue:work

# Clear all caches
php artisan optimize:clear

# List all registered routes
php artisan route:list --path=api

# Tinker — interactive REPL for testing Eloquent queries
php artisan tinker

Frequently Asked Questions

Yes. Laravel holds the top position for PHP framework usage by a wide margin, with strong adoption in SaaS, e-commerce, and enterprise web applications. The ecosystem has matured significantly — Livewire, Inertia.js, and Filament have positioned Laravel as a viable full-stack framework without requiring a separate JavaScript framework for every project. The job market for Laravel developers remains strong, particularly in Europe, Southeast Asia, and the Middle East.

The service container is Laravel's dependency injection system. It resolves class dependencies automatically, which means your controllers and services receive their dependencies through constructor injection without you manually instantiating them. Understanding the container is what separates Laravel developers who write testable, maintainable code from those who string facades together and wonder why testing is painful.

Eloquent's N+1 problem occurs when you load a collection of models and access a relationship on each one without eager loading. The fix is eager loading with the with() method: User::with('posts')->get() issues two queries instead of N+1. Laravel Debugbar or Telescope will show you exactly how many queries each request is firing — run it in development and you will find N+1 issues immediately.

Any operation that takes longer than 500ms and doesn't need to block the HTTP response belongs in a queue. Email sending, PDF generation, image processing, third-party API calls, webhook dispatching, and report generation are the common candidates. Laravel's queue system supports multiple drivers — Redis, database, SQS, Beanstalkd — with a consistent API across all of them.

Sanctum is for SPA authentication and simple API token issuance. It uses cookie-based sessions for same-domain SPAs and opaque tokens for mobile apps and simple API clients. Passport is a full OAuth2 server implementation for when you need to issue tokens to third-party clients, support authorization code flows, or run a public API that external developers authenticate against. Most applications need Sanctum. Choose Passport only when you're building an OAuth2 provider.

#Laravel framework tutorial#Laravel PHP development#Laravel Eloquent ORM#Laravel service container#Laravel queue system#Laravel authentication#PHP web development 2026
Share this article:
FNA Technology

Written by

FNA Technology

Team Member at FNA Technology

FNA Technology is a software development company specializing in AI, mobile apps, and web solutions.

Work with us