Building Your First Full-Stack Application with Laravel 12
Overview
Modern web development often feels fragmented, requiring developers to juggle disparate libraries for routing, authentication, and database management.
Prerequisites
To follow this tutorial, you should have a baseline understanding of HTML, CSS, and PHP. You need
Key Libraries & Tools
- Laravel Framework: The core PHP framework providing the foundation for our app.
- Blade Templating: Laravel's powerful engine for creating dynamic HTML layouts.
- Eloquent ORM: An Active Record implementation for interacting with your database using PHP syntax instead of raw SQL.
- Tailwind CSS: A utility-first CSS framework for rapid UI development.
- Daisy UI: A component library built on top of Tailwind to provide pre-styled elements like cards and buttons.
- Vite: The modern build tool used to compile and serve your frontend assets.
- Laravel Cloud: A specialized platform for deploying and hosting Laravel applications with minimal configuration.
Project Setup and Routing
Setting up a new project starts with the Laravel installer. Running the command laravel new chirper initiates a wizard where you select your database (we recommend SQLite for beginners) and testing framework. Once initialized, the directory structure might look daunting, but most of your work happens in three places: app/ (logic), resources/ (UI), and routes/ (URLs).
Defining Your First Route
Routes are the entry points of your application. In routes/web.php, you map a URL to a specific action. Initially, Laravel points the root URL (/) to a default welcome page.
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('home');
});
Creating a Blade Layout
Code duplication is the enemy of maintainability. Instead of rewriting the HTML head and navigation on every page, we use a Blade Layout Component. Create a file at resources/views/components/layout.blade.php. This file acts as a shell, using the $slot variable to inject content from specific pages.
<!-- resources/views/components/layout.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ $title ?? 'Chirper' }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<nav>...</nav>
<main>
{{ $slot }}
</main>
</body>
</html>
You can then wrap your home page content in this layout using the <x-layout> tag:
<!-- resources/views/home.blade.php -->
<x-layout>
<x-slot:title>Welcome to Chirper</x-slot>
<h1>Latest Chirps</h1>
</x-layout>
The MVC Pattern and Controllers
Laravel follows the Model-View-Controller (MVC) architectural pattern. Think of a restaurant: the Controller is the waiter taking orders, the Model is the kitchen preparing data, and the View is the plated meal presented to the customer. To keep our web.php file clean, we move logic into a Controller.
Generate a controller using Artisan:
php artisan make:controller ChirpController --resource
The --resource flag is a powerhouse. It generates seven methods (index, create, store, show, edit, update, destroy) that cover every standard CRUD (Create, Read, Update, Delete) operation.
Passing Data to Views
Inside ChirpController.php, the index method fetches data and hands it to the view:
public function index()
{
$chirps = [
['author' => 'Dev Harper', 'message' => 'Hello Laravel!', 'time' => '1m ago'],
];
return view('home', ['chirps' => $chirps]);
}
Update your route to point to this controller:
use App\Http\Controllers\ChirpController;
Route::get('/', [ChirpController::class, 'index']);
Database Management with Migrations and Eloquent
To store real data, we need a database schema. Laravel uses Migrations, which are essentially version control for your database. Instead of sharing SQL dumps, you share migration files.
Creating the Chirps Table
Run php artisan make:migration create_chirps_table. In the generated file, define your columns:
public function up(): void
{
Schema::create('chirps', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->cascadeOnDelete();
$table->string('message');
$table->timestamps();
});
}
Apply the changes by running php artisan migrate. This command creates the table in your database.sqlite file.
The Eloquent Model
An Eloquent Model is a PHP class that represents a table. To interact with the chirps table, create a Chirp model:
php artisan make:model Chirp
Inside the model, define Mass Assignment protections and relationships. Relationships allow you to access the author of a chirp without writing complex JOIN queries.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Chirp extends Model
{
protected $fillable = ['message'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Implementing Authentication
While Laravel offers starter kits like Breeze or Jetstream, building basic authentication manually provides deep insight into how sessions work.
Registration and Hashing
When a user registers, we must never store their password in plain text. Laravel provides the Hash facade for this. Use an Invocable Controller—a controller with only one method—to handle registration logic.
public function __invoke(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|unique:users',
'password' => 'required|confirmed|min:8',
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
Auth::login($user);
return redirect('/')->with('success', 'Account created!');
}
Protecting Routes with Middleware
Middleware acts as a filter. If you want to ensure only logged-in users can post chirps, use the auth middleware in your routes:
Route::middleware(['auth'])->group(function () {
Route::post('/chirps', [ChirpController::class, 'store']);
Route::delete('/chirps/{chirp}', [ChirpController::class, 'destroy']);
});
Securing the App with Authorization Policies
Authentication identifies who the user is; Authorization determines what they can do. You don't want User A deleting User B's chirps.
Generate a policy: php artisan make:policy ChirpPolicy --model=Chirp.
public function update(User $user, Chirp $chirp): bool
{
return $chirp->user()->is($user);
}
In your controller, simply call authorize before performing an update:
public function update(Request $request, Chirp $chirp)
{
$this->authorize('update', $chirp);
// logic to update the chirp
}
Syntax Notes
- Artisan Commands: Always use
php artisanfollowed by a command (e.g.,make:model,migrate). It is the heartbeat of Laravel productivity. - Blade Directives: Use
@symbols for logic in views.@foreach,@if, and@authmake templates readable. - CSRF Protection: Every HTML form must include the
@csrfdirective. This generates a hidden token that prevents cross-site request forgery attacks. - Route Model Binding: If a route is defined as
/chirps/{chirp}, Laravel automatically fetches theChirpmodel with that ID if you type-hint it in the controller method.
Practical Examples
- Micro-blogging: The Chirper app demonstrates real-time data entry and display.
- SaaS Dashboards: The MVC and Policy patterns are essential for building secure multi-tenant software.
- API Development: Laravel makes it trivial to return JSON instead of HTML views, allowing you to use the same logic for mobile apps.
Tips & Gotchas
- Mass Assignment Error: If you get a "MassAssignmentException," ensure you have added the column names to the
$fillablearray in your Model. - Eager Loading: Use
Chirp::with('user')->get()instead ofChirp::all(). This prevents the "N+1" query problem, where the app makes a separate database call for every single user's name. - Validation: Always validate on the server side. Client-side validation (HTML
requiredattribute) is for UX; server-side validation is for security. - Deployment: When moving to Laravel Cloud, ensure your environment variables (like
APP_KEY) are properly configured to keep your sessions secure.
