Pertama kali buat project Laravel 11, saya agak kaget. Folder yang biasanya numpuk di root project hilang setengah. Tidak ada lagi app/Http/Kernel.php, tidak ada app/Exceptions/Handler.php, bahkan config/ juga isinya jauh lebih sedikit. Saya kira install-nya rusak, tapi ternyata memang ini desain baru yang disebut "slim skeleton".
Kalau kamu terbiasa dengan Laravel 10 ke bawah, pasti butuh waktu adaptasi. Tapi setelah pakai beberapa minggu, saya justru merasa project jadi lebih bersih dan mudah dikelola. Di artikel ini, saya akan bedah perubahan struktur project Laravel 11 dan cara kerjanya.
Laravel 11 menghilangkan banyak boilerplate yang sebelumnya wajib ada. Tujuannya sederhana: kamu tidak perlu file yang tidak kamu pakai. Kalau butuh customisasi, baru kamu publish file-nya.
Perubahan utama yang langsung terlihat:
bootstrap/app.phpbootstrap/app.phproutes/console.php dan bootstrap/app.phpCoba bandingkan struktur folder Laravel 10 vs Laravel 11:
# Laravel 10 (lama)
app/
Console/
Kernel.php
Http/
Kernel.php
Middleware/
Authenticate.php
EncryptCookies.php
PreventRequestsDuringMaintenance.php
RedirectIfAuthenticated.php
TrimStrings.php
TrustHosts.php
TrustProxies.php
ValidateCsrfToken.php
VerifyCsrfToken.php
config/
app.php
auth.php
broadcasting.php
cache.php
cors.php
database.php
filesystems.php
hashing.php
logging.php
mail.php
queue.php
sanctum.php
services.php
session.php
view.php
routes/
api.php
channels.php
console.php
web.php
# Laravel 11 (baru)
app/
Http/
Middleware/
Authenticate.php
TrimStrings.php
ValidateCsrfToken.php
bootstrap/
app.php
providers.php
config/
app.php
routes/
console.php
web.php
Cukup drastis, kan? Dari 15+ file middleware, sekarang cuma 3. Config dari 15 file, sekarang cuma app.php. Semuanya ada di satu tempat: bootstrap/app.php.
Di Laravel 11, file bootstrap/app.php jadi jantung dari aplikasi. Di sinilah kamu konfigurasi middleware, exception handling, dan routing. Tampilannya sangat berbeda dari versi sebelumnya:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// Konfigurasi middleware di sini
$middleware->throttleApi('60,1');
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions) {
// Custom exception handling di sini
})->create();
Perhatikan beberapa hal penting:
health otomatis membuat health check endpoint di /upKalau kamu butuh API routes, tinggal tambahkan parameter:
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
apiPrefix: 'api',
)
Ini langsung aktifkan api middleware group yang berisi throttle dan binding. Tidak perlu lagi daftar di Kernel.php seperti Laravel 10.
Laravel 11 hanya menyertakan 3 middleware bawaan:
Middleware lain yang dulu wajib ada sekarang di-apply otomatis oleh framework. Misalnya, TrustProxies dan PreventRequestsDuringMaintenance langsung dihandle oleh Laravel tanpa perlu file terpisah.
Kalau butuh custom middleware, caranya sekarang lebih simpel:
// app/Http/Middleware/EnsureUserIsAdmin.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureUserIsAdmin
{
public function handle(Request $request, Closure $next)
{
if (!$request->user() || !$request->user()->is_admin) {
abort(403, 'Unauthorized access');
}
return $next($request);
}
}
Daftarkan di bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
]);
// Atau sebagai middleware group
$middleware->group('admin-panel', [
\App\Http\Middleware\EnsureUserIsAdmin::class,
]);
})
Saya pribadi lebih suka pendekatan ini karena semua konfigurasi terpusat. Tidak perlu bolak-balik antara Kernel.php dan middleware file. Kalau kamu pernah baca tutorial tips trik Laravel Eloquent, konsep terpusat ini juga berlaku untuk ORM configuration.
Salah satu perubahan signifikan adalah hilangnya banyak file config dari folder config/. Laravel 11 hanya menyertakan config/app.php secara default. Config lain seperti database.php, cache.php, session.php tetap ada di framework, tapi tidak di-publish ke project kamu.
Kalau butuh customize, publish config-nya:
# Publish satu config tertentu
php artisan config:publish database
# Publish semua config (kembali ke cara lama)
php artisan config:publish --all
Ini approach yang bagus karena project kamu hanya punya file yang benar-benar perlu diubah. Sebelumnya, 15 file config menumpuk padahal 90% tidak pernah disentuh.
Untuk kamu yang pakai CI4 untuk REST API, konsep ini mirip dengan CI4 yang juga tidak mem-publish semua config secara default.
Dulu, exception handling tersebar di app/Exceptions/Handler.php dengan method report() dan render(). Sekarang semuanya di bootstrap/app.php:
->withExceptions(function (Exceptions $exceptions) {
// Report semua exception ke log
$exceptions->report(function (\Throwable $e) {
if (app()->has('sentry')) {
app('sentry')->captureException($e);
}
});
// Custom render untuk API error
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Resource not found',
'status' => 404
], 404);
}
});
// Ignore exception tertentu (tidak di-report)
$exceptions->dontReport(MaintenanceModeException::class);
// Custom HTTP status code
$exceptions->render(function (PaymentRequiredException $e) {
return response('Payment required', 402);
});
})
Untuk aplikasi production, kamu juga bisa bikin custom error page:
$exceptions->render(function (NotFoundHttpException $e) {
return response()->view('errors.404', [], 404);
});
Laravel 11 tetap mendukung route model binding dan form request seperti biasa, tapi ada beberapa enhancement:
// routes/web.php - Route model binding dengan implicit binding
use App\Models\Post;
Route::get('/posts/{post:slug}', function (Post $post) {
return view('posts.show', compact('post'));
});
// Form Request dengan validasi yang lebih fleksibel
// app/Http/Requests/StorePostRequest.php
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', 'unique:posts'],
'body' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
'tags' => ['nullable', 'array'],
'tags.*' => ['exists:tags,id'],
];
}
// Custom message bahasa Indonesia
public function messages(): array
{
return [
'title.required' => 'Judul wajib diisi',
'title.unique' => 'Judul sudah dipakai, coba yang lain',
'body.required' => 'Konten artikel tidak boleh kosong',
];
}
}
Untuk API development, saya sarankan juga baca tentang fitur PHP 8.x yang banyak dipakai di Laravel 11, seperti named arguments, enum, dan fiber.
Di Laravel 10, artian command didaftarkan di app/Console/Kernel.php. Di Laravel 11, schedule dipindah ke routes/console.php:
// routes/console.php
<?php
use Illuminate\Support\Facades\Schedule;
// Schedule command harian
Schedule::command('app:cleanup-expired-sessions')->daily();
Schedule::command('app:send-newsletter')->weekly()->wednesdays()->at('09:00');
// Schedule closure
Schedule::call(function () {
Cache::forget('stale-data');
})->everySixHours();
// Artisan command tetap di app/Console/Commands/
// Tidak perlu register di Kernel.php lagi
Ini lebih intuitif karena schedule sekarang ada di routes/ bersama web dan console routes. Satu tempat untuk semua scheduling logic.
Laravel 11 juga menyederhanakan service provider. Secara default hanya ada AppServiceProvider:
// app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Model;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind singleton
$this->app->singleton(InvoiceService::class, function ($app) {
return new InvoiceService(
$app->make(PaymentGateway::class)
);
});
}
public function boot(): void
{
// Strict mode untuk model (recommended untuk production)
Model::shouldBeStrict(! $this->app->isProduction());
// Disable lazy loading (mencegah N+1 query problem)
Model::preventLazyLoading(! $this->app->isProduction());
}
}
Provider lain seperti AuthServiceProvider, EventServiceProvider, dan RouteServiceProvider dihapus. Registrasi event dan observer sekarang otomatis via attribute:
// app/Models/Post.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use App\Observers\PostObserver;
#[ObservedBy([PostObserver::class])]
class Post extends Model
{
// ...
}
// app/Observers/PostObserver.php
class PostObserver
{
public function creating(Post $post): void
{
$post->slug = Str::slug($post->title);
}
public function deleted(Post $post): void
{
// Hapus file terkait
Storage::delete($post->thumbnail_path);
}
}
Migration di Laravel 11 tidak banyak berubah secara syntax, tapi ada beberapa fitur baru yang berguna:
// Migration dengan foreign key yang lebih clean
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->string('thumbnail')->nullable();
$table->enum('status', ['draft', 'published', 'archived'])->default('draft');
$table->timestamp('published_at')->nullable();
$table->timestamps();
$table->softDeletes();
// Index untuk query yang sering dipakai
$table->index(['status', 'published_at']);
$table->index(['user_id', 'status']);
});
Satu hal yang sering dilupakan: database session. Kalau kamu pakai session database driver, jalankan:
php artisan session:table
php artisan migrate
Kalau kamu mau upgrade project lama, ini langkah-langkah yang saya rekomendasikan:
^11.0 di composer.jsonphp artisan laravel:upgrade akan bantu migrasi otomatisbootstrap/app.php# Upgrade step by step
composer require laravel/framework:^11.0
php artisan laravel:upgrade
# Atau install fresh dan pindahkan code
composer create-project laravel/laravel my-project
Laravel 11 dengan slim skeleton-nya memang butuh adaptasi kalau kamu terbiasa dengan versi sebelumnya. Tapi setelah pakai beberapa minggu, project terasa lebih bersih. Tidak ada lagi file-file yang menumpuk tanpa pernah disentuh.
Konsep "publish kalau butuh" bikin project lebih ringan dan fokus. Middleware terpusat di bootstrap/app.php juga memudahkan maintenance. Kalau kamu baru mau mulai belajar Laravel, ini waktu yang tepat karena Laravel 11 punya learning curve yang lebih rendah dibanding versi-versi sebelumnya.
Pertanyaan: apakah kamu sudah coba Laravel 11 atau masih setia di versi lama? Share pengalamanmu di kolom komentar!