As a developer I have always been curious about different frameworks that can make building web applications easier and more enjoyable. My general test for new frameworks is how quickly a pastebin style or imgur style webiste can be built.
Recently I decided to dive into Laravel one of the most popular PHP frameworks. After watching ThePrimeagen ‘Launching a site in one day… WITH PHP?’
It was amazing to see how quickly a site can be developed using Laravel and how great there starter kits are. Laravel has great first party packages that allow you to rapidly develop a website with ease and alot is already built into the framework
Starter Kits
Livewire Starter Kit
The Livewire starter kit provides a starting point for building Laravel applications with a Laravel Livewire frontend.
Livewire is a pretty cool way to build dynamic, reactive, frontend UIs using just PHP. It’s great when you want to pull in frammeworks like Vue or React
The Livewire starter kit utilizes Livewire, Tailwind, and the Flux UI component library.
Vue Starter Kit
There is also a Vue starter although I havent used this starter kit directly
React Starter Kit
There is also a React starter although I havent used this starter kit directly
Auth
When I first started building websites with Laravel one of my biggest concerns was authentication. I wanted something secure, easy to set up, and flexible. Auth is a PITA ass at times and getting it right is hard. Thankfully Laravel ships with some auth already built in with its ‘session’ guard.
In Laravel middleware acts like a gatekeeper for routes. It can check requests before they reach controllers making sure users meet certain conditions like being logged in.
Using the auth middleware is as easy as attaching it to your route definition. Here’s how you can do it:
Route::view('dashboard', 'dashboard')
->middleware(['auth', 'verified', CheckIfUserDisabled::class])
->name(name: 'dashboard');
We also have more middlewars attached to this route being both ‘verified’ and ‘CheckIfUserDisabled’ which is my own custom middleware. And is used to verify if the account has been disabled. Its a little jank but it works. Problem is it only verifies if the user is disabled on request to authenticated pages rather then on the login request. This could most likely to rebuilt in a better way and do a check on login if the account is disabled buuuut I haven’t got aroound in doing that yet.
class CheckIfUserDisabled
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && !Auth::user()->active) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/')->with('status', 'Your account has been disabled.');
} else {
return $next($request);
}
}
}
Custom Middleware
Adding custom middleware in Laravel is very easy thankfully, an example middleware is we can create is an isAdmin middleware. Rather then using Sanctum we can quickly create a middleware which verifies the authenticated user isAdmin()
class IsAdmin
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle($request, Closure $next)
{
if (!Auth::check() || !Auth::user()->isAdmin()) {
abort(403, 'Unauthorized');
}
return $next($request);
}
}
isAdmin is a function we create in the User model
public function isAdmin()
{
return $this->is_admin;
}
Database Migrations and Seeders
As your project grows keeping your database structure organized and consistent becomes crucial. Laravel’s migrations and seeders are powerful tools that help you manage your database schema and populate it with test data—all from PHP code.
Here’s an example migration that creates three tables: users, password_reset_tokens, and sessions:
<?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('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};
To run the migrations we can use:
php artisan migrate
And we can rollback with below:
php artisan migrate:rollback
SEEEDERS!
Seeders allow you fill your database with sample or default data. This is super handy for testing or when you want to quickly set up a fresh install of your app.
Here is a simple example of a seeder for the users table:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
public function run(): void
{
DB::table('users')->insert([
'name' => 'Test User',
'email' => 'test@example.com',
'password' => Hash::make('password'),
]);
}
}
Seeders can be run with:
php artisan db:seed
Why Use Migrations?
- Consistency: Everyone on your team has the same database structure.
- Version Control: Track changes to your schema over time.
- Easy Testing: Seeders make it easy to fill your database with test data.
- Rollback: If something goes wrong, you can easily undo changes.
Deployment
This stack is built specifically to harness the performance gains of Laravel Octane. If you prefer FrankenPHP, Swoole, or RoadRunner, you can swap in your preferred Octane server and get instant speed boosts, zero boot time, and async capabilities.
Awesome Docker Compose
A stacked docker-compose.yml starts a full stack so you don’t have to piece things together yourself. Included below:
- Traefik: Reverse proxy that handles routing load balancing SSL termination and secure access to your services.
- PostgreSQL: Robust production-grade database backend.
- Redis: Caching and queueing for improved response times and background job processing.
- Minio: S3-compatible object storage for your application’s assets file uploads and backups.
- Typesense: A powerful typo-tolerant search engine to deliver fast relevant search results to your users.
- pgAdmin & pghero: Tools for database management and performance monitoring so you can keep an eye on your data and queries.
- Backup Service: Automated scheduled backups to protect your valuable data and make disaster recovery painless.
- System Monitoring: Glances and Netdata provide real-time insights into your infrastructure’s health resource usage and performance.
Security Hardened
Security is baked in from the start. The stack follows best practices such as:
- User authentication for exposed services (like pgAdmin Netdata and Traefik dashboards).
- Restricted container privileges and user permissions.
- Secure network segmentation between containers.
- Automatic SSL via Traefik for encrypted traffic.