Laravel: My First Real Framework
Before React, before Spring Boot, there was Laravel. How I went from writing spaghetti PHP to understanding MVC architecture, Blade templates, and why frameworks exist. A journey back to early 2019 when everything clicked.
The PHP Spaghetti Era
Before I discovered Laravel, my PHP code looked like this:
<?php
// index.php - 500 lines of chaos
$conn = mysqli_connect("localhost", "root", "", "mydb");
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$name = $_POST["name"];
$email = $_POST["email"];
// No validation, no escaping, SQL injection paradise
$sql = "INSERT INTO users (name, email) VALUES ('$name', '$email')";
mysqli_query($conn, $sql);
}
$result = mysqli_query($conn, "SELECT * FROM users");
?>
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<h1>Users</h1>
<?php while($row = mysqli_fetch_assoc($result)): ?>
<p><?php echo $row['name']; ?> - <?php echo $row['email']; ?></p>
<?php endwhile; ?>
<form method="POST">
<input name="name" placeholder="Name">
<input name="email" placeholder="Email">
<button type="submit">Add User</button>
</form>
</body>
</html>
HTML, PHP, SQL, all in one file. No structure. No organization. A security nightmare.
But it worked. So I kept doing it.
The Breaking Point
My “app” grew to 15 PHP files. Each one a mix of database queries, business logic, and HTML. Finding anything took forever. Changing one thing broke three others.
A senior developer reviewed my code:
“Have you considered using a framework?”
“What’s a framework?”
Silence.
Enter Laravel
He suggested Laravel. “It’s PHP, but organized. You’ll hate it at first, then wonder how you lived without it.”
He was right on both counts.
Installation: Welcome to Composer
In my raw PHP days, “installing” something meant downloading a zip file and including it with require. Laravel uses Composer, PHP’s package manager.
# Install Composer first (one-time)
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
# Create new Laravel project
composer create-project --prefer-dist laravel/laravel myapp "5.6.*"
I specifically used Laravel 5.6 — it was the stable version in early 2019.
The download took forever. So many dependencies. What is all this stuff?
The Project Structure That Terrified Me
myapp/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ └── Middleware/
│ ├── Models/
│ └── Providers/
├── config/
├── database/
│ ├── migrations/
│ └── seeds/
├── public/
├── resources/
│ └── views/
├── routes/
│ └── web.php
├── storage/
├── vendor/
├── .env
└── composer.json
My brain: Where do I put my code? What are migrations? What’s middleware? Why are there so many folders?
But here’s the thing: each folder has ONE purpose. Unlike my single-file chaos, Laravel separates concerns:
routes/— URL definitionsapp/Http/Controllers/— Request handling logicresources/views/— HTML templates (Blade)database/migrations/— Database structureconfig/— Configuration files.env— Environment variables (passwords, API keys)
My First Route
In routes/web.php:
Route::get('/', function () {
return view('welcome');
});
That’s it. Visit /, return the welcome view. No index.php?page=home. No URL parsing. Just clean routes.
Adding my own:
Route::get('/hello', function () {
return 'Hello, World!';
});
Route::get('/users', function () {
return view('users.index');
});
Route::get('/users/{id}', function ($id) {
return "User ID: " . $id;
});
The {id} syntax captures URL parameters. /users/5 → $id = 5. Magic.
Blade Templates: PHP’s JSX Moment
Remember my inline PHP mess? Blade fixed that.
Create resources/views/users/index.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Users</title>
</head>
<body>
<h1>All Users</h1>
@foreach($users as $user)
<div class="user-card">
<h3>{{ $user->name }}</h3>
<p>{{ $user->email }}</p>
</div>
@endforeach
@if(count($users) === 0)
<p>No users found.</p>
@endif
</body>
</html>
Key differences from raw PHP:
{{ $var }}instead of<?php echo htmlspecialchars($var); ?>@foreach/@endforeachinstead of<?php foreach(): ?>@if/@endifinstead of<?php if(): ?>
The {{ }} syntax automatically escapes output. No more XSS vulnerabilities because I forgot htmlspecialchars().
Layouts: Stop Repeating Yourself
Every page needs <html>, <head>, navigation, footer. With raw PHP, I was copying this everywhere.
Blade has layouts.
Create resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>@yield('title') - My App</title>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/users">Users</a>
<a href="/about">About</a>
</nav>
<main class="container">
@yield('content')
</main>
<footer>
© 2019 My App
</footer>
<script src="/js/app.js"></script>
</body>
</html>
Now any page can extend this:
{{-- resources/views/users/index.blade.php --}}
@extends('layouts.app')
@section('title', 'Users')
@section('content')
<h1>All Users</h1>
@foreach($users as $user)
<div class="user-card">
<h3>{{ $user->name }}</h3>
<p>{{ $user->email }}</p>
</div>
@endforeach
@endsection
Change the navigation once in layouts/app.blade.php, every page updates. This was the “component” moment before I knew what components were.
Controllers: Separating Logic from Routes
My routes file was getting crowded. Time to use controllers.
php artisan make:controller UserController
This creates app/Http/Controllers/UserController.php:
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
$users = User::all();
return view('users.index', ['users' => $users]);
}
public function show($id)
{
$user = User::findOrFail($id);
return view('users.show', ['user' => $user]);
}
public function create()
{
return view('users.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email|unique:users',
]);
User::create($validated);
return redirect('/users')->with('success', 'User created!');
}
}
And the routes become simple:
Route::get('/users', 'UserController@index');
Route::get('/users/create', 'UserController@create');
Route::post('/users', 'UserController@store');
Route::get('/users/{id}', 'UserController@show');
Or even simpler with resource routing:
Route::resource('users', 'UserController');
This one line creates ALL standard CRUD routes. I couldn’t believe it.
Eloquent: Database Without SQL Strings
Remember my SQL injection paradise? Eloquent is Laravel’s ORM.
The User model (app/User.php):
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password'];
}
Now I can do:
// Get all users
$users = User::all();
// Find by ID
$user = User::find(1);
// Find or 404
$user = User::findOrFail(1);
// Create
User::create([
'name' => 'Saurav',
'email' => 'saurav@example.com',
'password' => bcrypt('secret')
]);
// Update
$user->name = 'New Name';
$user->save();
// Delete
$user->delete();
// Query builder
$activeUsers = User::where('active', true)
->orderBy('created_at', 'desc')
->take(10)
->get();
No SQL strings. No mysqli_real_escape_string(). Eloquent handles it all.
Migrations: Version Control for Databases
My old approach: Write SQL in phpMyAdmin, hope I remember what I changed.
Laravel migrations:
php artisan make:migration create_users_table
Creates database/migrations/2019_02_10_000000_create_users_table.php:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->timestamps(); // created_at, updated_at
});
}
public function down()
{
Schema::dropIfExists('users');
}
}
Run migrations:
php artisan migrate
Every team member runs the same migrations. Database changes are versioned. Roll back mistakes with php artisan migrate:rollback. Revolutionary.
The Artisan CLI
php artisan became my best friend:
# Create things
php artisan make:controller ProductController
php artisan make:model Product -m # -m creates migration too
php artisan make:middleware CheckAdmin
# Database
php artisan migrate
php artisan migrate:rollback
php artisan db:seed
# Development
php artisan serve # Start dev server on localhost:8000
php artisan tinker # Interactive PHP shell with Laravel loaded
# Cache
php artisan cache:clear
php artisan config:clear
php artisan view:clear
No more memorizing file locations. Artisan creates properly structured files in the right places.
The .env File: Configuration Done Right
My old approach: Hardcode database passwords in PHP files. Push to GitHub. Panic.
Laravel’s approach:
# .env (not committed to git!)
APP_NAME=MyApp
APP_ENV=local
APP_DEBUG=true
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
Access in code:
$appName = env('APP_NAME');
$dbHost = config('database.connections.mysql.host');
Different .env for development and production. Secrets stay secret.
What I Wish I’d Known Earlier
-
Read the documentation. Laravel’s docs are excellent. I wasted hours Googling things that were clearly explained in the official docs.
-
Use Artisan for everything. Don’t manually create files.
php artisan make:*ensures proper structure. -
Eloquent isn’t magic. It’s running SQL under the hood. Use
DB::enableQueryLog()to see what’s actually executing. -
Blade components exist.
@includeand@componentlet you create reusable pieces. I learned this way too late. -
The learning curve is front-loaded. The first week is confusing. The second week is productive. The third week you’re flying.
The Journey Continues
Laravel transformed how I thought about web development. Instead of chaos, I had structure. Instead of copy-pasting, I had reusable components. Instead of SQL injection vulnerabilities, I had Eloquent.
But I’d only scratched the surface. Laravel had authentication built-in. Form handling. Sessions. And something called “notifications” that would change how I thought about user communication.
P.S. — If you’re coming from raw PHP, Laravel will feel like overkill at first. “Why do I need all these folders for a simple app?” Give it two weeks. Build something real. You’ll never go back to spaghetti PHP. I promise.
Saurav Sitaula
Software Architect • Nepal